Skip to content

Commit 9721f60

Browse files
authored
feat: fallback to getUser() if the kid of the JWT is not found (#1080)
Because the `/.well-known/jwks.json` is heavily cached, a developer may rotate the standby key to in use faster than those caches expire. In that case the `getClaims()` method may receive a JWT signed with a key ID it doesn't recognize. Instead of failing with an error, it should reach out directly to the Auth server to verify the JWT.
1 parent 7665f94 commit 9721f60

File tree

2 files changed

+14
-12
lines changed

2 files changed

+14
-12
lines changed

src/GoTrueClient.ts

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -2968,7 +2968,7 @@ export default class GoTrueClient {
29682968
})
29692969
}
29702970

2971-
private async fetchJwk(kid: string, jwks: { keys: JWK[] } = { keys: [] }): Promise<JWK> {
2971+
private async fetchJwk(kid: string, jwks: { keys: JWK[] } = { keys: [] }): Promise<JWK | null> {
29722972
// try fetching from the supplied jwks
29732973
let jwk = jwks.keys.find((key) => key.kid === kid)
29742974
if (jwk) {
@@ -2992,7 +2992,7 @@ export default class GoTrueClient {
29922992
throw error
29932993
}
29942994
if (!data.keys || data.keys.length === 0) {
2995-
throw new AuthInvalidJwtError('JWKS is empty')
2995+
return null
29962996
}
29972997

29982998
this.jwks = data
@@ -3001,7 +3001,7 @@ export default class GoTrueClient {
30013001
// Find the signing key
30023002
jwk = data.keys.find((key: any) => key.kid === kid)
30033003
if (!jwk) {
3004-
throw new AuthInvalidJwtError('No matching signing key found in JWKS')
3004+
return null
30053005
}
30063006
return jwk
30073007
}
@@ -3066,12 +3066,16 @@ export default class GoTrueClient {
30663066
validateExp(payload.exp)
30673067
}
30683068

3069-
// If symmetric algorithm or WebCrypto API is unavailable, fallback to getUser()
3070-
if (
3069+
const signingKey =
3070+
!header.alg ||
3071+
header.alg.startsWith('HS') ||
30713072
!header.kid ||
3072-
header.alg === 'HS256' ||
30733073
!('crypto' in globalThis && 'subtle' in globalThis.crypto)
3074-
) {
3074+
? null
3075+
: await this.fetchJwk(header.kid, options?.keys ? { keys: options.keys } : options?.jwks)
3076+
3077+
// If symmetric algorithm or WebCrypto API is unavailable, fallback to getUser()
3078+
if (!signingKey) {
30753079
const { error } = await this.getUser(token)
30763080
if (error) {
30773081
throw error
@@ -3088,10 +3092,6 @@ export default class GoTrueClient {
30883092
}
30893093

30903094
const algorithm = getAlgorithm(header.alg)
3091-
const signingKey = await this.fetchJwk(
3092-
header.kid,
3093-
options?.keys ? { keys: options.keys } : options?.jwks
3094-
)
30953095

30963096
// Convert JWK to CryptoKey
30973097
const publicKey = await crypto.subtle.importKey('jwk', signingKey, algorithm, true, [

src/lib/helpers.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,9 @@ export function validateExp(exp: number) {
340340
}
341341
}
342342

343-
export function getAlgorithm(alg: 'RS256' | 'ES256'): RsaHashedImportParams | EcKeyImportParams {
343+
export function getAlgorithm(
344+
alg: 'HS256' | 'RS256' | 'ES256'
345+
): RsaHashedImportParams | EcKeyImportParams {
344346
switch (alg) {
345347
case 'RS256':
346348
return {

0 commit comments

Comments
 (0)