Creating an API Server

Creating a OAuth2 enabled resource server.

With oidc-spa, your frontend is meant to communicate with OAuth-enabled backend services, such as REST APIs, tRPC servers, or WebSocket endpoints, that accept JSON Web Tokens (JWTs) as access tokens.

These tokens are sent in the Authorization header and allow the backend to validate, decode, and use the claims to perform user-specific actions.

There are countless libraries for verifying JWTs, but if you’re building your backend in JavaScript (Node, Deno, or Web Workers), oidc-spa also provides a built-in utility to validate and decode access tokens that your client attaches to requests.

The great thing about JWT validation is that it works offline, there’s no need to contact your authorization server every time to ask “Is this token valid and issued by you?”

oidc-spa (server) simply fetches the public key published by your IdP once, then uses it to verify that each incoming token: • was signed by the IdP, • targets the expected audience, and • hasn’t expired.

This is a huge advantage for edge runtimes, since identity and authorization can be established locally no external round trips before executing user-specific logic.

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

And because access tokens issued through the Authorization Code Flow + PKCE are short-lived (typically 5 minutes or less), decoupling session lifetime from token validity isn’t an issue in practice.

Example with Express and Hono

Let’s say we have a Node.js REST API built with Express or Hono. We’ll create a function that takes an access token (and optionally a required role) and performs the following checks:

  • ✅ Validates that the token was issued by the expected IdP.

  • ✅ Ensures the token is still valid (not expired or tampered with).

  • ✅ Confirms the audience matches this specific API — meaning the token was minted for it.

  • ✅ Checks that the user has the required role, if one is specified.

If any of these conditions fail, we throw a specialized Hono error, which will cause the HTTP response to return 401 Unauthorized.

If the token passes all checks, the function returns the user’s ID (sub claim), allowing the rest of your API logic to identify who made the request.

Let's create a oidc.ts file:

Then you can enforce that some endpoints of your API requires the user to be authenticated, in this example we use Hono:

Testable example

This is a kitchen think example with the following stack:

The app is live here:

The frontend (Vite project):

The backend Node Todos App REST API with Express and Hono:

Last updated

Was this helpful?