JWT Validation
Learn how Wristband SDKs validate JWT access tokens issued by Wristband.
Access tokens issued by Wristband are JSON Web Tokens (JWTs) signed by your Wristband Application. An API that receives an access token as a Bearer token can check it without calling Wristband on every request, by verifying the token's signature and claims itself. JWT validation SDKs do this work.
They're used wherever an API accepts Bearer tokens, including:
- Machine authentication: APIs that receive access tokens from services using an M2M auth SDK.
- User authentication: APIs that receive a user's access token directly, such as from a frontend using the Token Endpoint.
Backend server auth SDKs use the same validation in their auth middleware's JWT strategy, so a backend server using that strategy doesn't need a separate JWT validation SDK.
Setting Up the JWT Validation SDK
Create the JWT validator once, when your API’s server starts up, and share that one instance across all requests. The only required value is the vanity domain of the Wristband Application that issues the tokens.
For example, using the TypeScript JWT Validation SDK:
import { createWristbandJwtValidator } from '@wristband/typescript-jwt';
export const wristbandJwtValidator = createWristbandJwtValidator({
wristbandApplicationVanityDomain: 'yourapp-yourcompany.us.wristband.dev',
});Validating a Request
Validating a request takes two steps: extracting the Bearer token from the Authorization header, and validating the token. For example, using the TypeScript JWT Validation SDK in an Express middleware function:
import { wristbandJwtValidator } from './wristband';
export async function requireJwt(req, res, next) {
try {
const token = wristbandJwtValidator.extractBearerToken(req.headers.authorization);
const result = await wristbandJwtValidator.validate(token);
if (!result.isValid) {
res.status(401).json({ error: 'Unauthorized' });
return;
}
req.auth = result.payload;
next();
} catch (error) {
res.status(401).json({ error: 'Unauthorized' });
}
}Some SDKs also integrate with the framework's own Bearer token authentication, so protected routes are validated without calling these functions directly (e.g. ASP.NET).
Extracting the Bearer Token
The token is read from the Authorization header of the request. For example:
GET https://api.yourapp.io/orders
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...Extracting the token throws an error if the header is missing, uses a scheme other than Bearer, has no token after the scheme, or appears more than once.
Validating the Token
The SDK performs the following checks on the token. It's only valid if every check passes:
- Format: The token has three parts (header, payload, and signature) that decode correctly.
- Algorithm: The token is signed with RS256, the only algorithm the SDK accepts.
- Issuer: The
issclaim matches your Wristband Application's vanity domain (e.g.https://yourapp-yourcompany.us.wristband.dev). - Expiration: The
expclaim hasn't passed, and thenbfclaim, if present, has. - Signature: The token's signature is valid for the signing key identified by the
kidin the token's header.
The SDK doesn't check the aud claim. If your API needs to restrict which clients can call it, check the token's claims yourself after validation.
Validation Result
Validating a token returns a result instead of throwing an error. The result contains the following fields:
| Field | Type | Description |
|---|---|---|
isValid | boolean | Whether the token passed every check. |
payload | object or undefined | The decoded token payload. Only present when isValid is true. |
errorMessage | string or undefined | A description of the check that failed. Only present when isValid is false. |
For example, using the TypeScript JWT Validation SDK:
const result = await wristbandJwtValidator.validate(token);
if (result.isValid) {
const userId = result.payload.sub;
} else {
console.debug(result.errorMessage);
}Token Payload
The payload is the token's decoded claims, exactly as Wristband issued them. The SDK doesn't add, remove, or change any claims. Standard claims such as sub, iss, and exp are available as typed fields. Every other claim, including Wristband claims like tnt_id and your custom claims, is available by name.
See JWTs and Signing Keys for more on the structure of Wristband access tokens.
Error Messages
When a check fails, errorMessage describes which one. For example:
| Error Message | When It Occurs |
|---|---|
No token provided | The token is empty. |
Invalid JWT format | The token doesn't have three parts. |
Invalid JWT encoding | The token's header or payload can't be decoded. |
Algorithm <alg> not allowed. Expected one of: RS256 | The token isn't signed with RS256. |
Invalid issuer. Expected <expected>, got <actual> | The iss claim doesn't match your Application's vanity domain. |
Token has expired | The exp claim has passed. |
Token not yet valid | The nbf claim hasn't passed yet. |
Token header missing kid (key ID) | The token's header has no kid. |
Failed to get signing key: <details> | No signing key in the JWKS matches the token's kid, or the JWKS endpoint couldn't be reached. |
Invalid signature | The token's signature doesn't match the signing key. |
Token validation failed | An unexpected error occurred during validation. If the error has its own message, that message is returned instead. |
The exact messages vary by SDK. Use them for logging and debugging, not to decide how to respond.
Signing Keys
To verify a token's signature, the SDK needs the public key of the key pair that signed it. It gets public keys from your Application's JSON Web Key Set (JWKS) endpoint. For example:
GET https://yourapp-yourcompany.us.wristband.dev/api/v1/oauth2/jwksEach key in the JWKS has a key ID (kid), which matches the kid in the header of the tokens it signed.
Signing Key Caching
The SDK caches signing keys in memory by key ID, so it only calls the JWKS endpoint the first time it sees a new key ID. After that, tokens signed with the same key are validated without any network calls.
Two settings control the cache:
jwksCacheMaxSize: The maximum number of signing keys to keep. When the cache is full, the least recently used key is removed. The default is 20.jwksCacheTtl: How long each key stays cached before it's fetched again. By default, keys stay cached until they're removed by the size limit.
If your Application's signing keys change, tokens signed with a new key have a new key ID. The SDK doesn't have that key cached, so it fetches the JWKS again and picks up the new key automatically.
JWT Validation SDK Configuration
Setting names and units may vary by SDK.
| Setting | Required | Default | Description |
|---|---|---|---|
wristbandApplicationVanityDomain | Yes | N/A | The vanity domain of the Wristband Application that issues the tokens. Used to build the expected issuer and the JWKS endpoint URL. |
jwksCacheMaxSize | No | 20 | The maximum number of signing keys cached in memory. |
jwksCacheTtl | No | No expiration | How long signing keys stay cached before they're fetched again. |
Updated about 10 hours ago