Sessions that never expire or persist for 30 days violate CWE-613 (Insufficient Session Expiration) and OWASP A07 (Identification & Authentication Failures). A session stolen via XSS, network interception, or a shared computer remains valid indefinitely, giving attackers a permanent foothold. NIST 800-53 AC-12 requires session termination after inactivity. For applications touching financial data, health records, or admin functions, indefinite sessions convert a one-time credential theft into permanent account control. Even a 7-day JWT without refresh rotation means a stolen token is valid for a full week after discovery.
High because overly long-lived sessions convert temporary access — from a stolen device, XSS attack, or public terminal — into persistent unauthorized control over an account.
Separate short-lived access tokens (15 minutes) from refresh tokens (7 days, rotated on use). Set explicit maxAge on session cookies:
// JWT access token — short-lived
const accessToken = jwt.sign({ userId }, process.env.JWT_SECRET!, { expiresIn: '15m' })
// Session cookie — explicit maxAge in seconds
res.setHeader('Set-Cookie', serialize('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 30 * 60, // 30 minutes
}))
For admin panels and payment flows, enforce absolute session expiry independent of activity (e.g., 4-hour hard cap even for active sessions).
ID: security-hardening.auth-session.session-timeout
Severity: high
What to look for: List all session configuration locations and for each, look for session expiry configuration in auth setup. For JWT, check the expiresIn claim and whether refresh token expiry is reasonable. For server-side sessions, check maxAge or ttl settings. Verify that the timeout is enforced server-side (not just a client-side cookie expiry that can be modified). For sensitive applications, idle timeout of ≤30 minutes is expected.
Pass criteria: Sessions or access tokens expire within a reasonable period: access tokens no more than 2 hours, idle session timeout no more than 8 hours for standard apps, and no more than 30 minutes for sensitive operations involving financial data, health data, or admin functions. Expiry is enforced server-side.
Fail criteria: Tokens or sessions never expire, have expiry >7 days without a refresh mechanism, or only rely on client-side cookie expiry without server-side validation.
Skip (N/A) when: The application uses a fully managed auth provider (Clerk, Auth0) that handles session lifecycle externally and you have verified their default timeout settings are appropriate.
Detail on fail: Specify the timeout value. Example: "JWT tokens issued with expiresIn: '30d' — 30-day tokens are excessively long-lived" or "Sessions have no maxAge configured — they persist indefinitely until the user manually logs out"
Remediation: Set appropriate expiry for tokens and sessions:
// JWT: short-lived access token + refresh token pattern
const accessToken = jwt.sign(
{ userId: user.id, role: user.role },
process.env.JWT_SECRET!,
{ expiresIn: '15m' } // 15 minutes
)
const refreshToken = jwt.sign(
{ userId: user.id, tokenFamily: tokenFamilyId },
process.env.REFRESH_SECRET!,
{ expiresIn: '7d' } // 7 days, rotated on use
)
// Session cookies: explicit maxAge
res.setHeader('Set-Cookie', serialize('session', sessionId, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 30 * 60, // 30 minutes in seconds
path: '/',
}))
For high-sensitivity operations (admin panels, payment flows), implement absolute session expiry independent of activity.