How to fix PGRST107: Invalid JWT in Supabase

SupabaseINTERMEDIATEHIGHUpdated Dec 22, 2025· 145 views

Supabase surfaces PostgREST’s PGRST107 code whenever it cannot decode or verify the bearer JWT, so the request is rejected before any policies or SQL run. This article walks through inspecting the Authorization header, checking the JWKS/signing key configuration, and refreshing tokens after key rotation.

Quick fix

Fixes PGRST107

curl -v -X POST https://your-project.supabase.co/rest/v1/widgets   -H "apikey: YOUR_ANON_KEY"   -H "Authorization: Bearer YOUR_JWT"   -H "Content-Type: application/json"   -d '{"name":"test"}'
  1. Confirm the Authorization header and JWT structure
  2. Verify the JWT matches this Supabase project’s signing keys
  3. Refresh sessions or reissue tokens after rotating keys
  4. Validate tokens with the Auth server as a last resort
supabase — error
curl -v -X POST https://your-project.supabase.co/rest/v1/widgets -H "apikey: YOUR_ANON_KEY" -H "Authorization: Bearer YOUR_JWT" -H "Content-Type: application/json" -d '{"name":"test"}'
PGRST107: Invalid JWT

What this error means

PostgREST sits in front of every Supabase REST, realtime, and Storage request and enforces HTTP semantics before PostgreSQL ever sees the payload. The "Invalid JWT" version of PGRST107 means PostgREST rejected the `Authorization: Bearer <token>` header because the token could not be parsed, decoded, or verified with the project's signing keys. Supabase relies on JWTs issued by Supabase Auth (or tokens you mint yourself) to carry the `role`, `exp`, `kid`, and other claims that secure row-level policies. When anything in that chain breaks—the header is missing, the structure is malformed, the token uses the wrong `kid`, or the token is expired—PostgREST bails out with the 401/415 PGRST107 body before your query touches the database.

Typical symptoms

  • Every fetch/axios/curl call to https://<project>.supabase.co/rest/v1 or realtime endpoints returns HTTP 401/405 with { code: "PGRST107", message: "Invalid JWT" } despite supplying a bearer token.
  • Supabase logs show PostgREST logging an "Invalid JWT" event right after you rotate signing keys or switch to a custom JWT.
  • Token-dependent features (edge functions, Row Level Security policies, Storage uploads) suddenly reject requests while anonymous GETs still work.
  • Third-party JWTs you mint yourself start returning PGRST107 whenever the `kid`, `iss`, or `alg` does not match the project's JWKS.
  • A user session that previously worked stops sending valid claims (the project returns null from `supabase.auth.getSession()`).

Common causes

  1. The Authorization header is missing, spelled incorrectly, or the value does not start with "Bearer".
  2. The JWT contains fewer than three dot-separated segments, so PostgREST cannot base64-decode the header/payload/signature.
  3. The presented token was signed with a different JWT secret or signing key (wrong `iss`/project URL) than the current Supabase project.
  4. The token expired (outdated `exp`) or you rotated signing keys before every cache that verifies JWKS had a chance to refresh.
  5. Custom tokens are missing required claims (`role`, `kid`, etc.) or the claims are malformed, so PostgREST treats the token as tampered.

How to fix "PGRST107: Invalid JWT"

1Confirm the Authorization header and JWT structure

Dump the incoming headers before they reach Supabase and make sure the request is still sending a bearer token. Many errors are simply typos or missing headers when running curl/fetch from the command line.

You can use curl to see exactly what reaches Supabase:

bash
curl -v -X POST https://your-project.supabase.co/rest/v1/widgets   -H "apikey: YOUR_ANON_KEY"   -H "Authorization: Bearer YOUR_JWT"   -H "Content-Type: application/json"   -d '{"name":"test"}'

The Authorization line must exist, start with Bearer, and the token should have three dot-separated parts. If the header disappears before Supabase sees it, a proxy or middleware is stripping it and you will need to preserve the header or re-add it before forwarding the request.

2Verify the JWT matches this Supabase project’s signing keys

Supabase exposes the current key set (JWKS) at https://<project>.supabase.co/auth/v1/.well-known/jwks.json. Decode your token (for example with https://jwt.io or a library) and check the kid and alg fields match one of the keys in the JWKS response. If they do not match, you are presenting a token signed by another project or an out-of-date key.

You can also use the jose library to verify locally:

ts
import { createRemoteJWKSet, jwtVerify } from "jose";

const JWKS = createRemoteJWKSet(
  new URL("https://your-project.supabase.co/auth/v1/.well-known/jwks.json")
);

await jwtVerify(token, JWKS);

If this verification throws, the signature is invalid. Use Supabase Auth, the CLI supabase gen bearer-jwt, or the new signing keys page to mint a token that matches the active key.

3Refresh sessions or reissue tokens after rotating keys

When you rotate signing keys in the Supabase dashboard, the edge cache can keep the old key for ~10 minutes. During that window, tokens signed with the new key can trigger PGRST107 because PostgREST still trusts the old key. If you control the client, re-run supabase.auth.refreshSession() or sign the user out and back in so they receive a JWT signed with the current key.

If you rotate keys manually, wait at least 15–20 minutes before revoking the old key to ensure all caches (edge layers and custom runtimes) have the latest JWKS. The Signing Keys doc explains this timing and how to roll back if something breaks.

4Validate tokens with the Auth server as a last resort

Supabase Auth also exposes a convenience endpoint that verifies whether a token is valid:

bash
curl https://your-project.supabase.co/auth/v1/user   -H "apikey: <anon or publishable key>"   -H "Authorization: Bearer YOUR_JWT"

If this returns HTTP 200, Auth has already validated the signature and claims. If Auth rejects the token, fix the signing key/claims or regenerate the token with Supabase Auth before retrying the PostgREST request. Calling this endpoint is especially helpful when using shared secrets (legacy JWT secret or HS256 signing keys), because it avoids reinventing verification logic.

Advanced notes

PGRST107 lives in PostgREST’s earlier validation layer (group 1 and 3 errors) that checks HTTP headers and JWT verification before any SQL executes. PostgREST relies on the public keys supplied by Supabase Auth to validate the signature, so rotating keys or caching failures will surface here instead of as 403s deeper in your policies. When you rotate keys, run the CLI or dashboard guidance in the Signing Keys docs so you do not accidentally keep presenting tokens signed with untrusted keys.

Frequently asked questions

What does "PGRST107" mean?
PostgREST sits in front of every Supabase REST, realtime, and Storage request and enforces HTTP semantics before PostgreSQL ever sees the payload. The "Invalid JWT" version of PGRST107 means PostgREST rejected the Authorization: Bearer <token> header because the token could not be parsed, decoded, or verified with the project's signing keys. Supabase relies on JWTs issued by Supabase Auth (or tokens you mint yourself) to carry the role, exp, kid, and other claims that secure row-level policies.
How do I fix "PGRST107"?
Supabase surfaces PostgREST’s PGRST107 code whenever it cannot decode or verify the bearer JWT, so the request is rejected before any policies or SQL run. This article walks through inspecting the Authorization header, checking the JWKS/signing key configuration, and refreshing tokens after key rotation.
What causes "PGRST107"?
The Authorization header is missing, spelled incorrectly, or the value does not start with "Bearer". The JWT contains fewer than three dot-separated segments, so PostgREST cannot base64-decode the header/payload/signature. The presented token was signed with a different JWT secret or signing key (wrong iss/project URL) than the current Supabase project.
Is the "PGRST107" error serious?
Yes. This is a high-severity error that usually blocks work until it is fixed, but the steps above resolve it.

Official resources & further reading