Backend Token Validation

Creating a OAuth2 enabled resource server.

Now that you’ve set up oidc-spa in your web app, you can call your API like this:

const todos = fetch("/api/todos", { 
    headers: {
        Authorization: `Bearer ${await oidc.getAccessToken()}`
    }
});

Next, let’s implement the backend side of things.

When you implement the server GET /api/todos handler, you want to read the Authorization header. Use it to authenticate the user. Optionally, check permissions (roles/scopes) to authorize the request.

If you’re building a JavaScript backend (Express, Hono, tRPC, NestJS, etc.), oidc-spa provides utilities to validate and decode access tokens. Validation includes DPoP proof checks and replay protection.

More context

The server-side validation utilities in oidc-spa implement RFC 9068: JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens.

JWT validation works offline. There’s no need to contact your authorization server for every request.

oidc-spa/server fetches the public key published by your IdP once. It then uses it to verify that each incoming token:

  • was signed by the IdP

  • targets the expected audience

  • hasn’t expired

  • has a valid DPoP proof (if applicable)

This is a big win for edge runtimes. Identity and authorization can be established locally, with no external round trips.

To authorize certain routes or actions, you can perform additional checks on claims like groups or realm_access.roles.

Some IdPs don’t issue JWT access tokens by default and issue opaque access tokens instead.

Opaque access tokens can’t be validated in a provider-agnostic way like JWTs can. If your IdP issues opaque access tokens, you’ll need provider-specific tooling. In that case, you won’t be able to use oidc-spa/server.

Integration

Integration instruction for common HTTP framworks. This only covers REST APIs and RPC. For securing WebSocket connection see bellow.

JS Runtime level integration

WebSocket

WebSocket

Mock Modes

Mock Modes

TODO List Example

A TODO list example app built with Vite / React / TanStack Router on the frontend, and Node.js / Hono on the backend.

The app is live here:

Source code (REST API):

Source code (frontend):

Last updated

Was this helpful?