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
};
const { verifyAccessToken } = require('./tokens');
function requireAuth(req, res, next) {
const header = req.headers.authorization || '';
const [scheme, token] = header.split(' ');
if (scheme !== 'Bearer' || !token) {
return res.status(401).json({ error: 'missing_bearer_token' });
}
try {
const claims = verifyAccessToken(token);
req.user = { id: claims.sub, roles: claims.roles || [] };
return next();
} catch (err) {
if (err.name === 'TokenExpiredError') {
return res.status(401).json({ error: 'token_expired' });
}
return res.status(401).json({ error: 'invalid_token' });
}
}
function requireRole(role) {
return function (req, res, next) {
const roles = (req.user && req.user.roles) || [];
if (!roles.includes(role)) {
return res.status(403).json({ error: 'forbidden' });
}
return next();
};
}
module.exports = { requireAuth, requireRole };
const express = require('express');
const { signAccessToken, signRefreshToken, verifyRefreshToken } = require('./tokens');
const { authenticateUser, findUserById } = require('./userService');
const { requireAuth, requireRole } = require('./authMiddleware');
const router = express.Router();
const refreshCookieOptions = {
httpOnly: true,
secure: true,
sameSite: 'strict',
path: '/auth/refresh'
};
router.post('/login', async (req, res) => {
const { email, password } = req.body;
const user = await authenticateUser(email, password);
if (!user) {
return res.status(401).json({ error: 'invalid_credentials' });
}
res.cookie('refresh_token', signRefreshToken(user), refreshCookieOptions);
return res.json({ accessToken: signAccessToken(user) });
});
router.post('/refresh', async (req, res) => {
const token = req.cookies.refresh_token;
if (!token) {
return res.status(401).json({ error: 'missing_refresh_token' });
}
try {
const claims = verifyRefreshToken(token);
const user = await findUserById(claims.sub);
return res.json({ accessToken: signAccessToken(user) });
} catch (err) {
return res.status(401).json({ error: 'invalid_refresh_token' });
}
});
router.get('/admin/reports', requireAuth, requireRole('admin'), (req, res) => {
res.json({ ok: true, viewer: req.user.id });
});
module.exports = router;
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
payload = {
sub: user.id,
iss: 'https://auth.example.com',
aud: 'codesnips-api',
exp: 15.minutes.from_now.to_i,
iat: Time.now.to_i,
JWT issuance and verification without common footguns
export interface RetryOptions {
retries: number;
baseMs: number;
maxMs: number;
signal?: AbortSignal;
onRetry?: (attempt: number, delay: number, err: unknown) => void;
Exponential backoff with jitter for retries
export type Settled<R> =
| { status: 'fulfilled'; value: R }
| { status: 'rejected'; reason: unknown };
export interface ConcurrencyOptions {
limit: number;
Simple concurrency limiter for batch operations
#!/usr/bin/env bash
set -euo pipefail
export VAULT_ADDR="https://vault.internal:8200"
export VAULT_TOKEN="${VAULT_TOKEN:?missing VAULT_TOKEN}"
Secrets management with environment isolation and Vault
import { randomBytes, createHash } from "crypto";
import jwt from "jsonwebtoken";
import { RefreshTokenStore } from "./store";
const ACCESS_SECRET = process.env.ACCESS_SECRET!;
const ACCESS_TTL = "15m";
JWT access + refresh token rotation (conceptual)
package files
import (
"context"
"time"
Presigned S3 upload URLs (AWS SDK v2)
Share this code
Here's the card — post it anywhere.