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.decodeand trusts the payload withoutjwt.verify(orjwtVerify) in the same function accepts forged tokens. Decode-then-use-then-verify order is still a miss. - Reset tokens with no bound. A plain
resetTokencolumn without a hash, anexpiresAtwindow, 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."