Skip to content

fix: validate JWT audience and issuer claims#41

Open
alanzabihi wants to merge 1 commit intosupabase:mainfrom
alanzabihi:fix-jwt-aud-iss
Open

fix: validate JWT audience and issuer claims#41
alanzabihi wants to merge 1 commit intosupabase:mainfrom
alanzabihi:fix-jwt-aud-iss

Conversation

@alanzabihi
Copy link
Copy Markdown
Contributor

@alanzabihi alanzabihi commented Apr 24, 2026

What kind of change does this PR introduce?

Bug fix

What is the current behavior?

jwtVerify in verify-credentials.ts is called without audience or issuer options:

const { payload } = await jwtVerify(credentials.token, jwkSet)

Any JWT signed by a key in the project's JWKS is accepted, regardless of who issued it or who it was intended for. In setups where multiple services share signing keys (common in self-hosted Supabase), a token minted by Service A is accepted by Service B as valid user auth.

What is the new behavior?

Two new optional fields on SupabaseEnv: audience and issuer. When set, they're forwarded to jwtVerify's options. Tokens that don't match are rejected with InvalidCredentialsError.

New environment variables: SUPABASE_JWT_AUDIENCE and SUPABASE_JWT_ISSUER. Both are optional. Existing behavior is unchanged when they aren't set.

Files changed:

  • src/types.ts -- add audience? and issuer? to SupabaseEnv
  • src/core/resolve-env.ts -- read the new env vars
  • src/core/verify-credentials.ts -- pass them to jwtVerify
  • docs/security.md -- document the new options and why they matter
  • docs/environment-variables.md -- list the new vars

Tests:

The actual logic this PR adds is: "if the env field is set, pass it to jose." The claim validation itself happens inside jose. Every existing user mode test already calls makeEnv() without audience or issuer, so the backward-compatible path is implicitly covered.

The one test worth adding is a resolveEnv wiring test: does it correctly read SUPABASE_JWT_AUDIENCE and SUPABASE_JWT_ISSUER from the environment and surface them on SupabaseEnv? Happy to add that if you'd like.

jwtVerify was called without audience or issuer options, so any JWT
signed by a key in the JWKS was accepted regardless of who issued it
or who it was intended for. In setups where multiple services share
signing keys, a token from one service would pass verification on
another.

Add optional SUPABASE_JWT_AUDIENCE and SUPABASE_JWT_ISSUER env vars.
When set, they are forwarded to jose's jwtVerify options. Existing
behavior is unchanged when they are not set.
@alanzabihi alanzabihi marked this pull request as ready for review April 24, 2026 10:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant