Sessions expire after ≤30min inactivity; re-auth for sensitive ops
Why it matters
Indefinite sessions leave authenticated tokens valid on shared or stolen devices long after the user leaves — a session cookie captured from an unattended workstation grants full account access with no expiry. NIST 800-53 rev5 AC-12 (Session Termination) requires automatic logout after inactivity, and AC-11 mandates re-authentication before sensitive operations. CWE-613 (Insufficient Session Expiration) and OWASP A07 both cite missing session timeouts as enabling session hijacking attacks. Federal system guidance caps inactivity timeout at 30 minutes; operations like password changes require step-up authentication even within an active session.
Severity rationale
Medium because indefinite sessions enable session hijacking from unattended devices but require an attacker to already possess a valid token — exploitation is not fully remote.
Remediation
Set maxAge to 1800 seconds in your auth library config and require current-password re-verification before sensitive operations like password changes or account deletion.
// lib/auth.ts (next-auth)
export const { handlers, auth } = NextAuth({
session: {
strategy: 'jwt',
maxAge: 30 * 60, // 30 minutes absolute expiry
updateAge: 5 * 60 // slide the window every 5 minutes of activity
}
})
// app/api/account/delete/route.ts
export const POST = async (req: Request) => {
const session = await auth()
const { currentPassword } = await req.json()
const user = await db.user.findUnique({ where: { id: session!.user.id } })
const ok = await bcrypt.compare(currentPassword, user!.passwordHash)
if (!ok) return Response.json({ error: 'Re-authentication required' }, { status: 401 })
// proceed with deletion
}
For Supabase Auth, set SUPABASE_AUTH_JWT_EXPIRY to 1800. Clerk and Auth0 both expose session lifetime controls in their dashboard settings.
Detection
-
ID:
session-timeout -
Severity:
medium -
What to look for: Examine session management code. Quote the exact session configuration values found (e.g.,
maxAge: 1800,expiresIn: '30m'). Look for session timeout configuration in auth libraries (next-auth, Clerk, Auth0, etc.), middleware that tracks inactivity, or cookie settings that define session lifetime. Check formaxAge,expiresIn, or similar timeout parameters. Count every sensitive operation and for each classify whether re-authentication is required. -
Pass criteria: User sessions automatically expire after no more than 30 minutes (1800 seconds) of inactivity. Sensitive operations (password reset, data deletion, role changes) require the user to re-authenticate or provide current password. Quote the actual timeout value configured.
-
Fail criteria: Sessions have no timeout, or timeout is longer than 30 minutes, or sensitive operations proceed without additional authentication. Do not pass when session timeout is set to 0 or explicitly disabled.
-
Skip (N/A) when: The project has no user authentication.
-
Detail on fail: Specify the timeout issue. Example:
"Session timeout not configured in next-auth — sessions persist until manual logout. No re-authentication required for password change."or"Session maxAge set to 1 hour (3600s) — exceeds 30-minute requirement" -
Remediation: Configure session timeout and require re-authentication for sensitive operations:
// lib/auth.ts (next-auth example) import NextAuth from 'next-auth' import Credentials from 'next-auth/providers/credentials' export const { handlers, auth } = NextAuth({ providers: [ Credentials({ credentials: { email: {}, password: {} }, async authorize(credentials) { // Verify email + password return user } }) ], session: { maxAge: 30 * 60, // 30 minutes updateAge: 5 * 60 // Refresh token every 5 minutes }, callbacks: { async session({ session, token }) { return session } } }) // app/api/auth/password-change/route.ts export const POST = async (req: Request) => { const session = await getServerSession() const { currentPassword, newPassword } = await req.json() // Re-verify user password before allowing change const user = await db.user.findUnique({ where: { email: session.user.email } }) const isValid = await bcrypt.compare(currentPassword, user.passwordHash) if (!isValid) { return Response.json({ error: 'Current password incorrect' }, { status: 401 }) } // Proceed with password change }
External references
- nist:rev5 · AC-12 — Session Termination
- nist:rev5 · AC-11 — Device Lock / Session Lock
- fedramp:rev5 · AC-12 — FedRAMP AC-12 — 30-minute inactivity timeout
- cwe · CWE-613 — Insufficient Session Expiration
- owasp:2021 · A07 — Identification and Authentication Failures
Taxons
History
- 2026-04-18·v1.0.0·Initial import from gov-fisma-fedramp·automated