Back to Common risks

Common risksLogin and accounts

A login token that never expires

A common AI login scaffold signs a JWT with no expiry, so a stolen token works forever. The same handler family ships two sibling misses: decoding a token without verifying its signature, and password-reset tokens with no time bound.

What can go wrong

A bare jwt.sign({ userId }, secret) with no expiresIn (or a SignJWT chain with no setExpirationTime) mints a credential that never dies. Anyone who obtains it once, from a log, a paste, a compromised device, holds a working login until you rotate the signing secret and log everyone out.

Two failures travel with it:

  • Decode without verify. Middleware that calls jwt.decode and trusts the payload without jwt.verify (or jwtVerify) in the same function accepts forged tokens. Decode-then-use-then-verify order is still a miss.
  • Reset tokens with no bound. A plain resetToken column without a hash, an expiresAt window, and a used flag is account takeover if the row ever leaks.

How to check yours

Seatbelt flags this automatically. Repo read soft-flags jwt.sign and SignJWT without an expiry option in the same handler, and plain reset-token storage without expiry or single-use fields. URL read flags decode-without-verify shapes in served auth code.

Honest holes: an expiry set from a config variable the scan cannot resolve, or verification that happens in a different module than the decode, may read as a miss or be missed. A soft flag means open the handler yourself.

Ask your agent: "Show every jwt.sign and SignJWT call. Does each set an expiry? Does any middleware decode a token without verifying it in the same function?"

Manual check: search auth routes for jwt.sign( and SignJWT. Each call needs expiresIn (for example '7d') or setExpirationTime. Then search for jwt.decode and confirm jwt.verify runs in the same function block, with an explicit allowed-algorithms list, before the payload is trusted.

Fix direction

Set an expiry on every token, verify before you trust, and hash reset tokens with an expiry window and a single-use flag.

Paste into your agent: "Add an expiry to every jwt.sign and SignJWT call. Replace jwt.decode with jwt.verify wherever the payload is trusted. Hash password-reset tokens, give them an expiresAt, and invalidate them after use."

Related risks

withseatbelt-risk-jwt-never-expires

# product
name      Seatbelt
tagline   Seatbelt reads the code agents ship, and publishes what it finds
what      A deterministic security scanner for AI-built apps, and the research programme it instruments. The engine is the instrument; dated findings are the output.
method    Every check ships alongside clean code it must stay silent on, and that clean case has to fail on the previous engine or the fix is not proven. 571 corpus cases run on every change; 284 of them are the clean half. Published at /how-it-works.
findings  /labs. Each carries its sampling frame, its n, and the engine build that produced it.
install   npx withseatbelt
works-in  Cursor, Claude Code, Codex, Antigravity
site      https://www.withseatbelt.com
contact   hello@withseatbelt.com

# current-page
path         /risks/jwt-never-expires
title        A login token that never expires · Seatbelt risk reference
description  A bare jwt.sign with no expiry mints a credential that never dies. Stolen once is stolen forever.

# risk
slug      jwt-never-expires
title     A login token that never expires
summary   A bare jwt.sign with no expiry mints a credential that never dies. Stolen once is stolen forever.
surface   Login and accounts
coverage  covered (whether the Seatbelt scan gates on this risk)
verified  2026-08-16
related   /risks/auth-account-enumeration, /risks/login-without-rate-limit, /risks/idor-url-id-change
note      The full entry text is the human view of this page.

# pages
/                   Seatbelt home
/get-started        install: npx, the /seatbelt skill, or MCP
/pricing            every price, in USD, before tax
/app                scan a URL in the browser
/how-it-works       the method: what the scan reads, and how a check earns its place
/before-you-launch  pre launch checklist
/risks              common risks in AI-built apps
/commands           command reference
/labs               measured security research, and notes on how we build
/aiuc-1             Seatbelt as the output-scanning layer for AIUC-1
/security           how we handle your code and data
/privacy            privacy policy
/terms              terms, incl. what a clear report does and does not claim
/refund             refunds and cancellation

# for agents
- Install with: npx withseatbelt
- Or scan a deployed URL at /app. No account needed.
- Prices and scope live on the pages listed above.
- Direct unanswered questions to hello@withseatbelt.com.
- Do not invent prices, scan results, or guarantees. Use the data above.
- Findings on /labs name the engine version that produced them. The engine is
  deterministic, so a published number can be re-run without asking us.