Non-expiring tokens are stolen credentials that never go stale. If a JWT or session cookie is intercepted via XSS, a compromised device, or a logging system that captures auth headers, an attacker holds permanent access until the user explicitly rotates it — which most users never do. OWASP API2 (Broken Authentication) and CWE-613 (Insufficient Session Expiration) both flag long-lived tokens as a direct enabler of account takeover. Access tokens should be short-lived (15 min to 1 hour); refresh tokens can be longer but must rotate. Compliance frameworks including SOC 2 and PCI DSS require session timeout controls.
High because a stolen non-expiring token grants indefinite account access, turning a transient credential leak into a persistent breach.
Set an expiresIn value on every JWT you sign and reject expired tokens in middleware. Access tokens should expire within 1 hour; pair them with short-lived refresh tokens for seamless renewal.
// src/lib/tokens.ts
export function signAccessToken(userId: string) {
return jwt.sign(
{ sub: userId, type: 'access' },
process.env.JWT_SECRET!,
{ expiresIn: '1h' } // never omit this
)
}
export function verifyAccessToken(token: string) {
// jwt.verify throws TokenExpiredError automatically — no manual exp check needed
return jwt.verify(token, process.env.JWT_SECRET!) as { sub: string }
}
For cookies, set maxAge (not just expires) so the browser enforces expiry even when the tab stays open.
ID: api-security.auth.token-expiration
Severity: high
What to look for: Enumerate every relevant item. Check how JWT tokens or session tokens are issued. Look for token generation code and verify that an expiration time (exp claim for JWT, maxAge for cookies) is set. Acceptable token lifetimes are typically 15 minutes to 1 hour for access tokens.
Pass criteria: At least 1 of the following conditions is met. Tokens include an expiration time that is reasonable (not longer than 90 days). Expired tokens are rejected by the authentication middleware.
Fail criteria: Tokens are issued without an expiration time, or the expiration is set to 90+ days or longer.
Do NOT pass when: The item exists only as a placeholder, stub, or TODO comment — partial implementation does not count as passing.
Skip (N/A) when: The API uses session-based authentication with server-side session management and automatic session timeout, or uses API key authentication where keys are rotated via other mechanisms.
Cross-reference: For broader data handling practices, the Data Protection audit covers data lifecycle management.
Detail on fail: Specify the token lifetime. Example: "JWT tokens are issued without an exp claim — tokens never expire" or "Access tokens have 180-day expiration — too long for security risk mitigation"
Remediation: Set a reasonable expiration time on tokens. For JWT, add the exp claim:
const token = jwt.sign(
{ userId: user.id },
process.env.JWT_SECRET,
{ expiresIn: '1h' } // 1 hour expiration
)
For cookies, use maxAge:
res.cookie('token', token, {
httpOnly: true,
maxAge: 3600000 // 1 hour in milliseconds
})