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.
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.
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:
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.
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:
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.
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.
Supabase Auth also exposes a convenience endpoint that verifies whether a token is valid:
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.
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.
email_address_not_authorized: Email sending to this address is not authorized
Email address not authorized for sending in Supabase Auth
reauthentication_needed: Reauthentication required for security-sensitive actions
Reauthentication required for security-sensitive actions
no_authorization: No authorization header was provided
How to fix "no authorization header was provided" in Supabase
otp_expired: OTP has expired
How to fix 'otp_expired: OTP has expired' in Supabase
bad_oauth_state: OAuth state parameter is missing or invalid
How to fix 'bad_oauth_state: OAuth state parameter missing' in Supabase