Skip to content

Token Verification

Access tokens are HS256 JWTs signed with the project’s signing_key. Verification is local — no network call to the auth service. This keeps latency low and eliminates a single point of failure for request authorization.

const secret = new TextEncoder().encode(process.env.JWT_SECRET);
async function verifyAccessToken(token: string) {
const { payload } = await jwtVerify(token, secret);
return {
userId: payload.sub as string,
email: payload.email as string,
role: payload.role as string,
status: payload.status as string,
emailVerified: payload.email_verified as boolean,
};
}

After verification, inspect the claims:

if (payload.status === 'blocked') {
// Clear cookies, return 403
}
if (payload.role !== 'admin') {
return new Response('Forbidden', { status: 403 });
}
const privileges: string[] = payload.privileges || [];
if (!privileges.includes('edit')) {
return new Response('Forbidden', { status: 403 });
}

When checking cookies, try the short-lived access token first, then fall back to the refresh token for verification-only scenarios:

const jar = await cookies();
const token =
jar.get('yourapp_access_token')?.value ||
jar.get('yourapp_auth_token')?.value;

The jose library throws specific errors:

ErrorMeaningAction
JWTExpiredToken past expAttempt refresh
JWSSignatureVerificationFailedWrong signing keyReturn 401, clear cookies
JWTClaimValidationFailedInvalid claimsReturn 401, clear cookies
try {
const { payload } = await jwtVerify(token, secret);
// Valid
} catch (e) {
if (e instanceof errors.JWTExpired) {
// Try refresh
} else {
// Invalid token — clear auth state
}
}

We use jose — the standard JavaScript JWT library. It works in Node.js, Cloudflare Workers, Deno, and browsers.

Terminal window
pnpm add jose