Skip to content

fix(auth): return AuthInvalidJwtError from getClaims for expired JWT#2395

Merged
mandarini merged 1 commit into
masterfrom
fix/get-claims-expired-error
May 28, 2026
Merged

fix(auth): return AuthInvalidJwtError from getClaims for expired JWT#2395
mandarini merged 1 commit into
masterfrom
fix/get-claims-expired-error

Conversation

@mandarini

@mandarini mandarini commented May 22, 2026

Copy link
Copy Markdown
Contributor

Description

getClaims is documented to return { data, error }, but for expired JWTs it throws a plain Error('JWT has expired'). The throw originates in validateExp (lib/helpers.ts), which uses a generic Error. The outer catch in getClaims only converts AuthError instances to the return tuple, so the plain Error escapes and breaks the documented contract.

This PR wraps the single validateExp call site and rethrows as AuthInvalidJwtError, matching the pattern already used for invalid signatures a few lines below.

What changed?

  • packages/core/auth-js/src/GoTrueClient.ts: wrapped validateExp(payload.exp) in a try/catch that rethrows as AuthInvalidJwtError. The outer catch then surfaces it as { data: null, error }.
  • packages/core/auth-js/test/GoTrueClient.test.ts: updated the expired-JWT test to assert on the { data, error } return shape.

validateExp in lib/helpers.ts is intentionally not changed, so the standalone validateExp(0) browser test continues to pass.

Why was this change needed?

getClaims's signature-verification path already throws AuthInvalidJwtError and returns it via { data, error }. Expired tokens were the only error path violating the documented return type.

Closes #2394

Screenshots/Examples

Before:

try {
  await client.auth.getClaims()
} catch (e) {
  // e is a plain Error with message 'JWT has expired'
}

After:

const { data, error } = await client.auth.getClaims()
if (error) {
  // error.name === 'AuthInvalidJwtError'
  // error.message === 'JWT has expired'
}

Breaking changes

Small in practice. Anyone using the documented const { data, error } = await getClaims() pattern is unaffected (and actually benefits, since their code was previously crashing on expired tokens). The only group impacted is callers who wrote try/catch specifically to catch the raw Error thrown for expired tokens. Their migration is mechanical: move handling from the catch block to if (error). Error message is preserved and AuthInvalidJwtError is already exported from @supabase/auth-js.

  • This PR contains no breaking changes

Checklist

  • I have read the Contributing Guidelines
  • My PR title follows the conventional commit format: <type>(<scope>): <description>
  • I have run pnpm nx format to ensure consistent code formatting
  • I have added tests for new functionality (if applicable)
  • I have updated documentation (if applicable)

Additional notes

Landing as fix(auth) rather than fix(auth)!. The previous behavior contradicted the documented return type and the realistic blast radius is small (callers who built workarounds for the bug). Happy to relabel if reviewers prefer the louder signal.

@github-actions github-actions Bot added the auth-js Related to the auth-js library. label May 22, 2026
@pkg-pr-new

pkg-pr-new Bot commented May 22, 2026

Copy link
Copy Markdown

Open in StackBlitz

@supabase/auth-js

npm i https://pkg.pr.new/@supabase/auth-js@2395

@supabase/functions-js

npm i https://pkg.pr.new/@supabase/functions-js@2395

@supabase/postgrest-js

npm i https://pkg.pr.new/@supabase/postgrest-js@2395

@supabase/realtime-js

npm i https://pkg.pr.new/@supabase/realtime-js@2395

@supabase/storage-js

npm i https://pkg.pr.new/@supabase/storage-js@2395

@supabase/supabase-js

npm i https://pkg.pr.new/@supabase/supabase-js@2395

commit: cc1e9bc

@mandarini mandarini force-pushed the fix/get-claims-expired-error branch from d942277 to cc1e9bc Compare May 27, 2026 12:17
@mandarini mandarini marked this pull request as ready for review May 27, 2026 15:22
@mandarini mandarini requested review from a team as code owners May 27, 2026 15:22
@mandarini mandarini merged commit 0dc5f80 into master May 28, 2026
24 of 25 checks passed
@mandarini mandarini deleted the fix/get-claims-expired-error branch May 28, 2026 09:25
mandarini pushed a commit to supabase/supabase that referenced this pull request Jun 2, 2026
This PR updates @supabase/*-js libraries to version 2.107.0.

**Source**: manual

**Changes**:
- Updated @supabase/supabase-js to 2.107.0
- Updated @supabase/auth-js to 2.107.0
- Updated @supabase/realtime-js to 2.107.0
- Updated @supabase/postgest-js to 2.107.0
- Refreshed pnpm-lock.yaml

---

## Release Notes

## v2.107.0

## 2.107.0 (2026-06-02)

### 🚀 Features

- **auth:** remove navigator.locks-based mutex; introduce commit guard +
dispose() ([#2392](supabase/supabase-js#2392))
- **realtime:** allow httpSend to send binary payload
([#2400](supabase/supabase-js#2400))
- **supabase:** update X-Client-Info to structured metadata format
([#2359](supabase/supabase-js#2359))

### 🩹 Fixes

- **auth:** return AuthInvalidJwtError from getClaims for expired JWT
([#2395](supabase/supabase-js#2395))
- **auth:** recognize ?error= redirects in implicit grant gate
([#2407](supabase/supabase-js#2407))
- **auth): revert fix(auth:** encode client-id in oauth requests
([#2383](supabase/supabase-js#2383),
[#2417](supabase/supabase-js#2417))
- **postgrest:** return a structured error for non-JSON body on
successful responses
([#2398](supabase/supabase-js#2398))
- **release:** pin workspace:* sibling deps before JSR publish
([#2418](supabase/supabase-js#2418))
- **release:** publish gotrue-js legacy mirror via pnpm
([#2419](supabase/supabase-js#2419))

### ❤️ Thank You

- Claude Opus 4.7 (1M context)
- Claude Sonnet 4.6
- Eduardo Gurgel
- Guilherme Souza
- Katerina Skroumpelou @mandarini
- Omar Al Matar @Bewinxed
- youcef zr @youcefzemmar
- youcefzemmar

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
imor pushed a commit to supabase/supabase that referenced this pull request Jun 3, 2026
This PR updates @supabase/*-js libraries to version 2.107.0.

**Source**: manual

**Changes**:
- Updated @supabase/supabase-js to 2.107.0
- Updated @supabase/auth-js to 2.107.0
- Updated @supabase/realtime-js to 2.107.0
- Updated @supabase/postgest-js to 2.107.0
- Refreshed pnpm-lock.yaml

---

## Release Notes

## v2.107.0

## 2.107.0 (2026-06-02)

### 🚀 Features

- **auth:** remove navigator.locks-based mutex; introduce commit guard +
dispose() ([#2392](supabase/supabase-js#2392))
- **realtime:** allow httpSend to send binary payload
([#2400](supabase/supabase-js#2400))
- **supabase:** update X-Client-Info to structured metadata format
([#2359](supabase/supabase-js#2359))

### 🩹 Fixes

- **auth:** return AuthInvalidJwtError from getClaims for expired JWT
([#2395](supabase/supabase-js#2395))
- **auth:** recognize ?error= redirects in implicit grant gate
([#2407](supabase/supabase-js#2407))
- **auth): revert fix(auth:** encode client-id in oauth requests
([#2383](supabase/supabase-js#2383),
[#2417](supabase/supabase-js#2417))
- **postgrest:** return a structured error for non-JSON body on
successful responses
([#2398](supabase/supabase-js#2398))
- **release:** pin workspace:* sibling deps before JSR publish
([#2418](supabase/supabase-js#2418))
- **release:** publish gotrue-js legacy mirror via pnpm
([#2419](supabase/supabase-js#2419))

### ❤️ Thank You

- Claude Opus 4.7 (1M context)
- Claude Sonnet 4.6
- Eduardo Gurgel
- Guilherme Souza
- Katerina Skroumpelou @mandarini
- Omar Al Matar @Bewinxed
- youcef zr @youcefzemmar
- youcefzemmar

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
mandarini pushed a commit to supabase/ssr that referenced this pull request Jun 3, 2026
This PR updates `@supabase/supabase-js` to v2.107.0.

**Source**: manual

---

## Release Notes

## v2.107.0

## 2.107.0 (2026-06-02)

### 🚀 Features

- **auth:** remove navigator.locks-based mutex; introduce commit guard +
dispose() ([#2392](supabase/supabase-js#2392))
- **realtime:** allow httpSend to send binary payload
([#2400](supabase/supabase-js#2400))
- **supabase:** update X-Client-Info to structured metadata format
([#2359](supabase/supabase-js#2359))

### 🩹 Fixes

- **auth:** return AuthInvalidJwtError from getClaims for expired JWT
([#2395](supabase/supabase-js#2395))
- **auth:** recognize ?error= redirects in implicit grant gate
([#2407](supabase/supabase-js#2407))
- **auth): revert fix(auth:** encode client-id in oauth requests
([#2383](supabase/supabase-js#2383),
[#2417](supabase/supabase-js#2417))
- **postgrest:** return a structured error for non-JSON body on
successful responses
([#2398](supabase/supabase-js#2398))
- **release:** pin workspace:* sibling deps before JSR publish
([#2418](supabase/supabase-js#2418))
- **release:** publish gotrue-js legacy mirror via pnpm
([#2419](supabase/supabase-js#2419))

### ❤️ Thank You

- Claude Opus 4.7 (1M context)
- Claude Sonnet 4.6
- Eduardo Gurgel
- Guilherme Souza
- Katerina Skroumpelou @mandarini
- Omar Al Matar @Bewinxed
- youcef zr @youcefzemmar
- youcefzemmar
## v2.106.2

## 2.106.2 (2026-05-25)

### 🩹 Fixes

- **auth:** restore signup user response
([#2391](supabase/supabase-js#2391))
- **misc:** add react-native export condition for Hermes-safe resolution
([#2393](supabase/supabase-js#2393))

### ❤️ Thank You

- Myroslav Hryhschenko @BLOCKMATERIAL
- Vaibhav @7ttp

This PR was created automatically.

Co-authored-by: supabase-workflow-trigger[bot] <266661614+supabase-workflow-trigger[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

auth-js Related to the auth-js library.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

getClaims is raising a generic Error for expired token

2 participants