Open6

auth0/node-jwks-rsa が bun のプロジェクトでうまく動作しない

siropacasiropaca

概要

node / pnpm で動作していたバックエンドの auth0 認証処理を bun に移行したところ、認証が通らなくなった。

動作環境

移行前

nodejs: 20.10.0
pnpm: 8.14.0

移行後

bun: 1.2.0
nodejs: 22.13.0
siropacasiropaca

以下の処理のところでエラーが出ている。

import jwt from 'jsonwebtoken'
import jwksClient from 'jwks-rsa'
import { errAsync, ok, ResultAsync } from 'neverthrow'
import { getEnvVar } from '../lib/env-var'

...

/**
 * JWT の署名を検証するための公開鍵を取得する
 */
const getPublicKey = (accessToken: string): ResultAsync<string, Error> => {
  const { auth0JwksUrl } = getEnvVar()

  // JWT をデコードして kid を取得
  const decoded = jwt.decode(accessToken, { complete: true })

  if (decoded === null) {
    return errAsync(new Error('JWTのデコードに失敗しました'))
  }

  // JWKS から公開鍵を取得
  return ResultAsync.fromPromise(
    jwksClient({ jwksUri: auth0JwksUrl }).getSigningKey(decoded.header.kid),
    () => new Error('サインインキーの取得に失敗しました')
  ).andThen((key) => ok(key.getPublicKey()))
}

...
siropacasiropaca

bunnode-jwks-rsa を最新にしてもなおらないので、node-jwks-rsa を使用するのを諦めて、必要な部分だけ自前で実装することにする。

以下、ソースコード(一部簡略)

// lib/jwks/jwks-client.ts

import type { Jwks, JwksClient, Options, SigningKey } from './types'

/**
 * JWKS クライアントを作成し、署名鍵を取得するためのメソッドを提供する
 */
export function jwksClient(options: Options): JwksClient {
  return {
    /**
     * 指定された kid に関連付けられた署名鍵を取得する
     */
    async getSigningKey(kid?: string | null): Promise<SigningKey> {
      const jwks = await fetchJWKS(options.jwksUri)

      const signingKey = jwks.keys.find((key) => key.kid === kid)

      if (!signingKey || !signingKey.x5c || !signingKey.x5c[0]) {
        throw new Error('該当する署名鍵が見つかりませんでした')
      }

      return {
        toPem: () => x5cToPem(signingKey.x5c),
      }
    },
  }
}

/**
 * JWKS を取得する
 */
function fetchJWKS(jwksUri: string): Promise<Jwks> {
  return fetch(jwksUri)
    .then((response) => {
      if (!response.ok) {
        throw new Error('JWKSの取得に失敗しました')
      }

      return response.json()
    })
    .catch((error) => {
      throw new Error('JWKSの取得に失敗しました')
    })
}

/**
 * 最初の証明書 (x5c) を PEM 形式に変換する
 */
function x5cToPem(x5c: string[]): string {
  return `-----BEGIN CERTIFICATE-----\n${x5c[0]}\n-----END CERTIFICATE-----`
}
// lib/jwks/types.ts

export interface Options {
  jwksUri: string
}

export interface SigningKey {
  toPem(): string
}

export interface JwksClient {
  getSigningKey(kid?: string | null | undefined): Promise<SigningKey>
}

export interface Jwk {
  kty: 'RSA'
  use: 'sig'
  n: string
  e: string
  kid: string
  x5t: string
  x5c: string[]
  alg: 'RS256'
}

export interface Jwks {
  keys: Jwk[]
}
siropacasiropaca

修正前

import jwt from 'jsonwebtoken'
import jwksClient from 'jwks-rsa'
import { errAsync, ok, ResultAsync } from 'neverthrow'
import { getEnvVar } from '../lib/env-var'

...

/**
 * JWT の署名を検証するための公開鍵を取得する
 */
const getPublicKey = (accessToken: string): ResultAsync<string, Error> => {
  const { auth0JwksUrl } = getEnvVar()

  // JWT をデコードして kid を取得
  const decoded = jwt.decode(accessToken, { complete: true })

  if (decoded === null) {
    return errAsync(new Error('JWTのデコードに失敗しました'))
  }

  // JWKS から公開鍵を取得
  return ResultAsync.fromPromise(
    jwksClient({ jwksUri: auth0JwksUrl }).getSigningKey(decoded.header.kid),
    () => new Error('サインインキーの取得に失敗しました')
  ).andThen((key) => ok(key.getPublicKey()))
}

...

修正後

import jwt from 'jsonwebtoken'
import { ResultAsync, errAsync, ok } from 'neverthrow'
import { jwksClient } from '../libs/jwks'

import { getEnvVar } from '../libs/env-var'

...

/**
 * JWT の署名を検証するための公開鍵を取得する
 */
const getPublicKey = (accessToken: string): ResultAsync<string, Error> => {
  const { auth0JwksUrl } = getEnvVar()

  // JWT をデコードして kid を取得
  const decoded = jwt.decode(accessToken, { complete: true })

  if (decoded === null) {
    return errAsync(new Error('JWTのデコードに失敗しました'))
  }

  // JWKS から公開鍵を取得
  return ResultAsync.fromPromise(
    jwksClient({ jwksUri: auth0JwksUrl }).getSigningKey(decoded.header.kid),
    () => new Error('公開鍵の取得に失敗しました')),
  ).andThen((key) => ok(key.toPem()))
}

...
siropacasiropaca

正規の対応のやり方知ってる方いましたら教えて下さい 🙏