getClaims allowExpired
Describe the bug
await getClaims(token, { allowExpired: true }
still throws expiration error
Library affected
auth-js
Reproduction
No response
Steps to reproduce
No response
System Info
System:
OS: Windows 11 10.0.22631
CPU: (16) x64 AMD Ryzen 7 6800H with Radeon Graphics
Memory: 3.63 GB / 15.19 GB
Binaries:
Node: 24.9.0 - C:\Users\Meowster\AppData\Local\mise\installs\node\24.9.0\node.EXE
npm: 11.6.0 - C:\Users\Meowster\AppData\Local\mise\installs\node\24.9.0\npm.CMD
pnpm: 10.17.1 - C:\Users\Meowster\AppData\Local\mise\installs\pnpm\10.17.1\pnpm.EXE
bun: 1.3.1 - C:\Users\Meowster\.bun\bin\bun.EXE
Deno: 1.39.0 - C:\Users\Meowster\.deno\bin\deno.EXE
Browsers:
Chrome: 141.0.7390.123
Edge: Chromium (140.0.3485.54)
Internet Explorer: 11.0.22621.3527
npmPackages:
@supabase/supabase-js: ^2.58.0 => 2.75.0
Used Package Manager
bun
Logs
71 | // The session_id inside the JWT does not correspond to a row in the
72 | // sessions table. This usually means the user has signed out, has been
73 | // deleted, or their session has somehow been terminated.
74 | throw new errors_1.AuthSessionMissingError();
75 | }
76 | throw new errors_1.AuthApiError(_getErrorMessage(data), error.status || 500, errorCode);
^
AuthApiError: invalid JWT: unable to parse or verify signature, token has invalid claims: token is expired
__isAuthError: true,
status: 403,
code: "bad_jwt"
at handleError (C:\Users\Meowster\<redacted>\node_modules\.bun\@[email protected]\node_modules\@supabase\auth-js\dist\main\lib\fetch.js:76:11)
Validations
- [x] Follow our Code of Conduct
- [x] Read the Contributing Guidelines.
- [x] Read the docs.
- [x] Check that there isn't already an issue that reports the same bug to avoid creating a duplicate.
- [x] Make sure this is a Supabase JS Library issue and not an issue with the Supabase platform. If it's a Supabase platform related bug, it should likely be reported to supabase/supabase instead.
- [x] Check that this is a concrete bug. For Q&A open a GitHub Discussion or join our Discord Chat Server.
- [x] The provided reproduction is a minimal reproducible example of the bug.
Thanks for reporting this @mwstr! I've investigated this issue thoroughly and found what's happening.
TL;DR: The allowExpired option currently only controls client-side JWT expiration validation. For symmetric JWTs (HS256, which is the default in Supabase), the code makes a server API call via getUser() to verify the signature, and the server validates expiration regardless of the client option. This makes allowExpired effectively non-functional for the common use case.
What's happening
When you call getClaims(token, { allowExpired: true }) with an HS256 JWT:
- Client-side expiration check is skipped (line 3652-3655 in
GoTrueClient.ts) - But then the code calls
getUser(token)to verify the signature (line 3667) - The GoTrue server validates the JWT (including expiration) and rejects it
The code path:
// Line 3652-3655: Client-side check (skipped when `allowExpired: true`)
if (!options?.allowExpired) {
validateExp(payload.exp)
}
// Line 3657-3663: For HS256, signingKey will be null
const signingKey = /* ... can't verify symmetric keys client-side ... */
// Line 3666-3669: Falls back to server validation
if (!signingKey) {
const { error } = await this.getUser(token) // Server rejects expired token!
if (error) {
throw error
}
}
Why this happens
Symmetric algorithms (HS256) require the secret key to verify signatures. The client doesn't have access to this secret (and shouldn't!), so it must call the server to verify. The server's JWT validation library validates expiration as part of its standard JWT verification process.
I am transferring this issue on the server side library.