Skip to content

Commit 4ac18e1

Browse files
committed
fix: set jwks_cached_at
1 parent daa2669 commit 4ac18e1

File tree

3 files changed

+62
-3
lines changed

3 files changed

+62
-3
lines changed

src/GoTrueClient.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
AUTO_REFRESH_TICK_THRESHOLD,
77
GOTRUE_URL,
88
STORAGE_KEY,
9+
JWKS_TTL,
910
} from './lib/constants'
1011
import {
1112
AuthError,
@@ -150,6 +151,7 @@ export default class GoTrueClient {
150151
* The JWKS used for verifying asymmetric JWTs
151152
*/
152153
protected jwks: { keys: JWK[] }
154+
protected jwks_cached_at: number
153155
protected autoRefreshToken: boolean
154156
protected persistSession: boolean
155157
protected storage: SupportedStorage
@@ -230,6 +232,7 @@ export default class GoTrueClient {
230232
this.lock = lockNoOp
231233
}
232234
this.jwks = { keys: [] }
235+
this.jwks_cached_at = Number.MIN_SAFE_INTEGER
233236
this.mfa = {
234237
verify: this._verify.bind(this),
235238
enroll: this._enroll.bind(this),
@@ -2607,7 +2610,9 @@ export default class GoTrueClient {
26072610

26082611
// try fetching from cache
26092612
jwk = this.jwks.keys.find((key) => key.kid === kid)
2610-
if (jwk) {
2613+
2614+
// jwk exists and jwks isn't stale
2615+
if (jwk && Date.now() - JWKS_TTL < this.jwks_cached_at) {
26112616
return jwk
26122617
}
26132618
// jwk isn't cached in memory so we need to fetch it from the well-known endpoint
@@ -2621,6 +2626,7 @@ export default class GoTrueClient {
26212626
throw new AuthInvalidJwtError('JWKS is empty')
26222627
}
26232628
this.jwks = data
2629+
this.jwks_cached_at = Date.now()
26242630
// Find the signing key
26252631
jwk = data.keys.find((key: any) => key.kid === kid)
26262632
if (!jwk) {

src/lib/constants.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ export const API_VERSIONS = {
3030
}
3131

3232
export const BASE64URL_REGEX = /^([a-z0-9_-]{4})*($|[a-z0-9_-]{3}$|[a-z0-9_-]{2}$)$/i
33+
34+
export const JWKS_TTL = 600000 // 10 minutes

test/GoTrueClient.test.ts

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,10 @@ import {
1313
authAdminApiAutoConfirmEnabledClient,
1414
GOTRUE_URL_SIGNUP_ENABLED_AUTO_CONFIRM_ON,
1515
authClient,
16-
authClientWithAsymmetricSession,
1716
GOTRUE_URL_SIGNUP_ENABLED_ASYMMETRIC_AUTO_CONFIRM_ON,
1817
} from './lib/clients'
1918
import { mockUserCredentials } from './lib/utils'
20-
import { Session } from '../src'
19+
import { JWK, Session } from '../src'
2120

2221
describe('GoTrueClient', () => {
2322
// @ts-expect-error 'Allow access to private _refreshAccessToken'
@@ -1149,3 +1148,55 @@ describe('GoTrueClient with storageisServer = true', () => {
11491148
expect(store.getItem('test-storage-key')).toEqual(JSON.stringify(newSession))
11501149
})
11511150
})
1151+
1152+
describe('fetchJwk', () => {
1153+
let fetchedUrls: any[] = []
1154+
1155+
const cases = [
1156+
{
1157+
desc: 'jwk exists but cache is stale',
1158+
jwks: { keys: [{ kid: '123', kty: 'RSA', key_ops: ['verify'] }] },
1159+
jwksCachedAt: Number.MIN_SAFE_INTEGER,
1160+
fetchedUrlsLength: 1,
1161+
},
1162+
{
1163+
desc: 'jwk does not exist and cache is stale',
1164+
jwks: { keys: [{ kid: '234', kty: 'RSA', key_ops: ['verify'] }] },
1165+
jwksCachedAt: Number.MIN_SAFE_INTEGER,
1166+
fetchedUrlsLength: 1,
1167+
},
1168+
{
1169+
desc: 'jwk exists in cache',
1170+
jwks: { keys: [{ kid: '123', kty: 'RSA', key_ops: ['verify'] }] },
1171+
jwksCachedAt: Number.MAX_SAFE_INTEGER,
1172+
fetchedUrlsLength: 0,
1173+
},
1174+
{
1175+
desc: 'jwk does not exist in cache',
1176+
jwks: { keys: [{ kid: '234', kty: 'RSA', key_ops: ['verify'] }] },
1177+
jwksCachedAt: Number.MAX_SAFE_INTEGER,
1178+
fetchedUrlsLength: 1,
1179+
},
1180+
]
1181+
1182+
beforeEach(() => {
1183+
fetchedUrls = []
1184+
})
1185+
1186+
cases.forEach((c) => {
1187+
test(`${c.desc}`, async () => {
1188+
// override fetch to return a hard-coded JWKS
1189+
authWithAsymmetricSession['fetch'] = async (url: RequestInfo | URL, _options = {}) => {
1190+
fetchedUrls.push(url)
1191+
return new Response(
1192+
JSON.stringify({ keys: [{ kid: '123', kty: 'RSA', key_ops: ['verify'] }] })
1193+
)
1194+
}
1195+
authWithAsymmetricSession['jwks'] = c.jwks as { keys: JWK[] }
1196+
authWithAsymmetricSession['jwks_cached_at'] = c.jwksCachedAt
1197+
// @ts-ignore 'Allow access to private fetchJwk'
1198+
await authWithAsymmetricSession.fetchJwk('123')
1199+
expect(fetchedUrls).toHaveLength(c.fetchedUrlsLength)
1200+
})
1201+
})
1202+
})

0 commit comments

Comments
 (0)