Skip to Content

Verify API access tokens with Node.js and JWKS

Before returning protected data, your API must verify the access token intended for that API. Decoding a JWT does not verify its signature. This small Node.js example uses jose to check the signature, trusted issuer, API audience and required time claims.

Download and run the example

Requires Node.js 22 or later. Save these five files in the same directory:

npm ci npm test

The tests generate temporary RSA keys and signed tokens locally. They require no tenant, account, SMS provider or production credentials. They test valid tokens, expiration, wrong issuer and audience, missing claims, future nbf, a different signing key, an unsupported algorithm and sender-constrained tokens without a proof.

These are integration fixtures, not a test of your live tenant. After the local tests, repeat verification with a short-lived access token issued for your own API in a test environment.

Configure trust before reading the token

Set AURIS_ISSUER, AURIS_API_AUDIENCE and AURIS_JWKS_URI from your tenant and API configuration. The issuer must match the value your authorization server emits exactly; the audience must identify the receiving API. Do not derive trusted values from an unverified token or use a token-provided key URL.

The CLI reads the token from standard input and prints only success or failure. Supply it using your local secret-handling workflow; do not paste tokens into public issue reports or include them in process arguments. No token is included in this download.

# Set the three AURIS_* environment variables using your environment's secret workflow. node verify.mjs # Supply the short-lived access token on stdin, then end the input stream.

In a server, construct the verifier once so the JWKS cache can be reused:

import { createAccessTokenVerifier } from './verifier.mjs' const verifyAccessToken = createAccessTokenVerifier({ issuer: process.env.AURIS_ISSUER, audience: process.env.AURIS_API_AUDIENCE, jwksUri: process.env.AURIS_JWKS_URI, }) // Extract the bearer token from the request's Authorization header. // Handle rejected verification as an authentication failure without echoing the token. const claims = await verifyAccessToken(accessToken) // Check your tenant, resource and action permissions before returning protected data.

What this example deliberately checks

CheckReason
RS256 signature from configured JWKSReject a forged token or a key chosen by the caller
Exact issuer and expected audienceReject another issuer’s token or one issued for another API
Required sub, iat, exp, iss, audReject incomplete identity and lifetime claims
Expiry and nbf, with five seconds of clock toleranceReject expired or not-yet-valid tokens
Sender-constrained cnf rejectedAvoid accepting a bound token as an ordinary bearer token

This is a bearer access-token example for an RS256 issuer. It does not implement a browser login callback, refresh tokens, immediate revocation, tenant/resource authorization or proof-of-possession validation. Use the DPoP guide for bound tokens. Never substitute an ID token for an API access token; configure a distinct API audience and apply your issuer’s token-type policy.

Next step

Connect this check to the hosted login flow, then test denied permissions and another organization’s data separately. If you need help choosing the tenant, application and API boundaries, request an Auris integration demo  or review Auris capabilities .

The verification APIs are documented in the upstream jose JWT verifier  and remote JWKS resolver .