Skip to content
~/mahadi hassan
← All posts

Supabase JWT Verification in NestJS

Verifying Supabase access tokens locally with JWKS inside NestJS guards — no per-request calls to Supabase, with JIT user mirroring and admin promotion.

1 min read
~/mahadi/blog

Supabase JWT Verification in NestJS

TypeScriptNestJS
On this page

Supabase is a fantastic identity provider, but its tokens still need to be verified by your own backend. This post covers how this site's NestJS API verifies Supabase JWTs without calling Supabase on every request.

The naive approach

The obvious implementation calls the Supabase admin API to validate each incoming token. It works, but it adds a network hop to every authenticated request and couples your API's uptime to someone else's.

Verifying locally with JWKS

Supabase publishes its signing keys at a JWKS endpoint. Fetch them once, cache them, and verify signatures locally. Token validation becomes a pure CPU operation measured in microseconds.

const jwks = createRemoteJWKSet(new URL(`${env.SUPABASE_URL}/auth/v1/.well-known/jwks.json`));
 
export async function verifyAccessToken(token: string) {
  const { payload } = await jwtVerify(token, jwks, {
    issuer: `${env.SUPABASE_URL}/auth/v1`,
  });
  return payload;
}

Wiring it into NestJS

A global guard extracts the bearer token, verifies it, and attaches the user to the request context. Routes opt out with a small @Public() decorator — public by exception, never by accident.

Just-in-time user mirroring

The API keeps a local users table mirrored from Supabase identities. On the first authenticated request, the guard upserts the user row. A few things to get right:

  • Match on the immutable Supabase user id, never the email
  • Promote configured admin emails on first login
  • Track lastSeenAt cheaply, without writing on every request

Failure modes

Plan for key rotation (the JWKS client handles it), clock skew (allow a small tolerance), and revocation (short token lifetimes make local verification safe). With those covered, auth stops being the scary part of the stack.

Comments

Loading comments…