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

概要
node / pnpm
で動作していたバックエンドの auth0 認証処理を bun
に移行したところ、認証が通らなくなった。
動作環境
移行前
nodejs: 20.10.0
pnpm: 8.14.0
移行後
bun: 1.2.0
nodejs: 22.13.0

以下の処理のところでエラーが出ている。
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()))
}
...

どうやら node-jwks-rsa
が bun
ではうまく動作しないらしい。
Repository
関連 Issue

bun
や node-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[]
}

修正前
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()))
}
...

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