# Supabase: “JWT expired”

> Supabase queries fail with JWT expired: the access token ran out and wasn’t refreshed. Keep auto refresh running, don’t store tokens, refresh on app resume.

Canonical: https://app369.com/fix/supabase-jwt-expired/
Last updated: 2026-11-08

Queries that worked a while ago start failing with 401 and JWT expired (PGRST303, or PGRST301 on older setups). Supabase access tokens are short-lived, and the client is supposed to refresh them in the background. Something stopped that: a token saved and reused, a header set once, or an app that was asleep when the refresh was due.

**Short answer**

Let supabase-js manage the session: don’t copy the access token into your own headers or storage. In React Native, start and stop auto refresh with AppState so the token refreshes when the app returns to the foreground. On the server, create a client per request from the user’s cookies instead of reusing a token. Raising the JWT expiry only hides the bug.

## **Why the token wasn’t refreshed, and the fix**

1. 01 ### **A token copied into a header or variable**
  > “Access tokens are designed to be short lived, usually between 5 minutes and 1 hour while refresh tokens never expire but can only be used once.” [**Supabase, User sessions**](https://supabase.com/docs/guides/auth/sessions) Creating the client with a fixed Authorization header, or passing an access token you saved earlier, sends the same token after it expires. Use the client’s own session, or read session.access_token fresh each time you need it.
2. 02 ### **A React Native app returning from the background**
  > “You should hook into your platform's foreground indication mechanism and call these methods appropriately to conserve resources.” [**Supabase, startAutoRefresh**](https://supabase.com/docs/reference/javascript/auth-startautorefresh) On mobile the OS can pause your app’s JavaScript while it is in the background, so a refresh due during that time is missed. Supabase’s docs say to hook into the platform’s foreground signal: start auto refresh when the app is active and stop it in the background.```
import { AppState } from 'react-native';
AppState.addEventListener('change', (state) => {
  if (state === 'active') supabase.auth.startAutoRefresh();
  else supabase.auth.stopAutoRefresh();
});
```
3. 03 ### **A browser tab in the background**
  > “On browsers the refresh process works only when the tab/window is in the foreground to conserve resources as well as prevent race conditions and flooding auth with requests.” [**Supabase, startAutoRefresh**](https://supabase.com/docs/reference/javascript/auth-startautorefresh) In browsers, the refresh runs only while the tab is visible. Requests from a background tab, a worker or a long-running loop can go out with an old token. Refresh when the tab becomes visible again, before you send requests.
4. 04 ### **Server code reusing one user token** A server, cron job or Edge Function that stores a user’s access token fails once it expires. Create a client per request from the incoming cookies or Authorization header. For background jobs that act on their own, use a server-side secret key and scope the queries yourself, since that key bypasses RLS.
5. 05 ### **What PGRST303 and PGRST301 mean**
  > “JWT claims validation or parsing failed.” [**Supabase, PostgREST error codes (PGRST303)**](https://supabase.com/docs/guides/api/rest/postgrest-error-codes) PostgREST returns 401 with the message JWT expired when the token’s exp claim is in the past. Current PostgREST reports it as PGRST303; versions before 13 reported PGRST301. Either way the fix is a fresh token, not a database change.

Checked against the vendor’s docs on November 8, 2026: [Supabase docs: User sessions](https://supabase.com/docs/guides/auth/sessions); [Supabase reference: startAutoRefresh](https://supabase.com/docs/reference/javascript/auth-startautorefresh); [Supabase docs: PostgREST error codes](https://supabase.com/docs/guides/api/rest/postgrest-error-codes).

## **Rather hand it over?**

Fixed scope, fixed price, paid upfront. The work happens in writing; no call needed.

- [from $450**App Fix Ticket**One reproducible bug in an existing mobile or web app, diagnosed and fixed.**See scope and start**](https://app369.com/fixed-price/flutter-firebase-bug-fix/)
- [from $450**AI-Built App Rescue**A Lovable, Bolt or Cursor app checked end to end in 3 days, then its worst problems fixed at a fixed price.****No issues worth fixing, no charge.**** If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.**See scope and start**](https://app369.com/fixed-price/ai-app-rescue/)

Related: [**Supabase Invalid Refresh Token logouts**](https://app369.com/fix/supabase-users-logged-out-refresh-token/) · [**Supabase JWSError JWSInvalidSignature**](https://app369.com/fix/supabase-jws-invalid-signature/) · [**Supabase logs users out on refresh**](https://app369.com/fix/supabase-session-lost-on-refresh/) · [**Supabase Email link is invalid or has expired**](https://app369.com/fix/supabase-email-link-invalid-or-expired/)

Questions

## **More on this error**

<details>

<summary>**Should I make the JWT expiry longer? **+</summary>

Supabase recommends the default of 1 hour for most apps and discourages going higher for security reasons. A longer expiry only delays the error if refresh is broken.

</details>

<details>

<summary>**Why does it fail right after the device wakes up? **+</summary>

The token expired while the device slept, and the first request went out before the refresh. On mobile, start auto refresh when the app becomes active; in browsers, wait for the session to refresh before sending queries.

</details>

<details>

<summary>**Can you fix it for me? **+</summary>

If one bug is the whole problem, the App Fix Ticket covers it: a written diagnosis first, then the fix. If the app has more going on, the Launch Check is a written check of the whole app. Each issue found is rated critical, high, medium or low, with where it is and how it was confirmed. No issues worth fixing, no charge. If the Launch Check finds nothing rated medium or higher, Simon refunds the full $450. You keep the report either way. This applies to the first 10 Launch Checks bought after October 30, 2026.

</details>

A different error?

## **Describe it and get a written quote**

Send the error and what you were doing. You get a written scope, a fixed price and a timeline; Simon replies by email, usually within one business day.

[**Describe your problem **→](https://app369.com/contact/) [**All fixes**](https://app369.com/fix/)