auth icon indicating copy to clipboard operation
auth copied to clipboard

getClaims allowExpired

Open mwstr opened this issue 5 months ago • 1 comments

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

mwstr avatar Oct 27 '25 00:10 mwstr

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:

  1. Client-side expiration check is skipped (line 3652-3655 in GoTrueClient.ts)
  2. But then the code calls getUser(token) to verify the signature (line 3667)
  3. 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.

mandarini avatar Nov 12 '25 16:11 mandarini