Docs · Integrate

Verify tokens

What's inside a Gatekeep ID token, how to check its Ed25519 signature against your JWKS, and the checks you must not skip.

Your OIDC library does all of this for you. Read on if you’re verifying tokens by hand, writing a backend that receives tokens from a frontend, or just want to know what’s going on.

Inside an ID token

A token is three base64url parts joined by dots: a header, a payload and a signature.

// header
{ "alg": "EdDSA", "typ": "JWT", "kid": "2026-10-active-1d8f12" }

// payload
{
  "iss": "https://acme.gatekeepauth.com",
  "sub": "01JB7Q4M9W2X8YDN3T5K6RZ0HV",
  "aud": "acme-notes",
  "exp": 1793318400,
  "iat": 1793317500,
  "email": "ada@acme.dev",
  "email_verified": true,
  "nonce": "n-0S6_WzA2Mj"
}

The checks

Reject the token unless all of these pass:

  1. alg is exactly EdDSA. Never accept none or anything else.
  2. The signature verifies with the key from /jwks.json whose kid matches the header.
  3. iss is exactly your issuer URL.
  4. aud is your client ID.
  5. exp is in the future (allow a minute of clock skew at most).
  6. nonce matches the one you sent, if you sent one.

The JWKS

{
  "keys": [{
    "kty": "OKP", "crv": "Ed25519", "use": "sig", "alg": "EdDSA",
    "kid": "2026-10-active-1d8f12",
    "x": "en_tDHOxBWpq8_etBFcqV09XqZTC0joJoqPEoq2Trls"
  }]
}

Signing keys rotate. A retiring key stays in the JWKS for 35 days so tokens it signed still verify. Cache the JWKS, and refetch it when you see a kid you don’t recognise.

Verify in Node.js with jose

import { createRemoteJWKSet, jwtVerify } from "jose"

const ISSUER = "https://acme.gatekeepauth.com"
const jwks = createRemoteJWKSet(new URL(`${ISSUER}/jwks.json`)) // caches and refetches on new kid

export async function verifyIdToken(idToken, expectedNonce) {
  const { payload } = await jwtVerify(idToken, jwks, {
    issuer: ISSUER,
    audience: "acme-notes",
    algorithms: ["EdDSA"],
  })
  if (expectedNonce && payload.nonce !== expectedNonce) throw new Error("nonce mismatch")
  return payload // payload.sub, payload.email
}

Access tokens are not for you

The access token’s aud is the issuer, not your app. It’s meant for Gatekeep’s /userinfo. Don’t use it to authorise requests to your own API by verifying it locally. Instead, sign users in with the ID token and use your own session.