JWT Access Tokens
The @lib/jwt package handles minting and verifying short-lived access tokens. /api/gate-access mints a token whenever a user completes SIWE or legacy authentication; subsequent calls can attach that token as Authorization: Bearer <token> to skip signature prompts.
Components
claims.ts– builds strongly typed payloads using Zod (AccessTokenPayloadSchema). It stores a SIWE projection, the original message hash (siwe_hash), scopes, tokenId, and standard JWT claims (iss,aud,exp, etc.).server.ts– wrapsjoseto sign/verify tokens using the configured algorithm (HS256,EdDSA, orES256). Secrets are loaded fromdapp/app/config/jwt.server.ts.client.ts– safe decode helpers for browsers (localStorage storage, expiration checks, scope helpers).
Configuration (dapp/app/config/jwt.server.ts)
JWT_ALG=EdDSA
JWT_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----..."
JWT_PUBLIC_KEY="-----BEGIN PUBLIC KEY-----..."
JWT_ISS=https://ritoswap.com
JWT_AUD=ritoswap.app,api.ritoswap.app
JWT_ACCESS_TTL=900 # seconds
JWT_CLOCK_TOLERANCE=5 # secondsThe config exports a frozen jwtServerConfig object consumed by lib/jwt/server.ts.
Never import jwt.server.ts (or any server-only config) into client bundles. The module is marked server-only for Next.js and will throw if it ends up in client code.
Usage
const claims = buildAccessClaims({
auth: 'siwe',
siweParsed,
originalSiweMessage,
issuer: jwtServerConfig.issuer,
audiences: jwtServerConfig.audiences,
accessTtlSec: jwtServerConfig.accessTtlSec,
scopes: ['gate:read'],
tokenId,
});
const token = await signAccessToken(claims);Routes that accept Bearer tokens call verifyAccessToken(token) and rehydrate the Zod-validated payload. Because the payload includes siwe_hash, the server can detect tampering even if someone manages to mutate the raw SIWE text.
File Tree
- claims.ts
- server.ts
- client.ts
- index.ts
- __tests__/claims.test.ts