javascript 122 lines · 3 tabs

Sign and Verify JWT Access Tokens in Express Auth Middleware

Shared by codesnips Aug 2026
3 tabs
const jwt = require('jsonwebtoken');

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET;
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET;
const ISSUER = 'api.example.com';
const AUDIENCE = 'example-web';

function signAccessToken(user) {
  const payload = { sub: user.id, roles: user.roles };
  return jwt.sign(payload, ACCESS_SECRET, {
    expiresIn: '15m',
    issuer: ISSUER,
    audience: AUDIENCE
  });
}

function signRefreshToken(user) {
  return jwt.sign({ sub: user.id }, REFRESH_SECRET, {
    expiresIn: '30d',
    issuer: ISSUER,
    audience: AUDIENCE
  });
}

function verifyAccessToken(token) {
  return jwt.verify(token, ACCESS_SECRET, {
    issuer: ISSUER,
    audience: AUDIENCE
  });
}

function verifyRefreshToken(token) {
  return jwt.verify(token, REFRESH_SECRET, {
    issuer: ISSUER,
    audience: AUDIENCE
  });
}

module.exports = {
  signAccessToken,
  signRefreshToken,
  verifyAccessToken,
  verifyRefreshToken
};
3 files · javascript Explain with highlit

This snippet shows how JWT-based authentication is wired into an Express app across three collaborating files: a token service that signs and verifies tokens, an auth middleware that gates protected routes, and the router that issues and consumes them.

In tokens.js, the signing and verification logic is isolated from the rest of the app so token concerns live in one place. signAccessToken embeds a minimal payload — the subject sub and a roles array — and sets a short expiresIn of fifteen minutes, because access tokens are meant to be ephemeral. The iss and aud claims are pinned so that verifyAccessToken can reject tokens minted for a different issuer or audience, which is a common oversight that lets tokens from one service be replayed against another. Refresh tokens use a separate secret and a longer lifetime, keeping the two token types cryptographically distinct so a leaked access secret cannot forge refresh tokens.

The requireAuth middleware in authMiddleware.js extracts the bearer token from the Authorization header, verifies it, and attaches the decoded claims to req.user. Failures are mapped to precise HTTP responses: a missing header yields 401, an expired token is distinguished by catching TokenExpiredError so the client knows to refresh rather than re-login, and any other verification error is treated as a generic 401. The companion requireRole factory demonstrates role-based access control layered on top of authentication — it reads req.user.roles and returns 403 when the claim is absent, keeping authorization declarative at the route level.

In authRoutes.js, the /login handler validates credentials and issues both tokens, returning the access token in the body while placing the refresh token in an httpOnly cookie to keep it out of reach of client-side scripts. The /refresh endpoint verifies the refresh token and mints a fresh access token without a new password check.

The key trade-off is statelessness: verification needs no database lookup, which scales well, but tokens cannot be revoked before they expire — hence the deliberately short access-token lifetime and a revocable refresh flow.


Related snips

Share this code

Here's the card — post it anywhere.

Sign and Verify JWT Access Tokens in Express Auth Middleware — share card
Link copied