JWT is often implemented in ways that actually reduce security: long-lived tokens stored in localStorage, with no way to revoke them. This is the authentication pattern I use so it stays convenient but remains accountable when a token leaks.
Short access, rotating refresh
The access token is short-lived so its abuse window is small. The refresh token lives longer and is used to obtain a new access token — but every time it is used, it rotates: the old refresh is revoked, a new one is issued.
This rotation is what gives the ability to detect theft. A token that should no longer be valid but is suddenly used again is a clear signal something is wrong.
Reuse detection: the signal of a stolen token
If a refresh token that has already rotated (and is therefore invalid) shows up again, chances are two parties hold the same token — the real user and a thief. The correct response: revoke the whole session chain and force a re-login.
Without rotation + reuse detection, a stolen token can be used silently until it expires naturally. With both, theft leaves a trail that can be acted on automatically.
Store in HttpOnly cookies, not localStorage
A token in localStorage can be read by any script on the page — one XSS hole and the token is gone. An HttpOnly + Secure + SameSite cookie cannot be read by JavaScript, moving the attack surface away from script injection.
The consequence is you need CSRF protection (e.g. a double-submit token), but that is a far better trade than leaving the token exposed to every third-party script.
Summary
Secure JWT auth is not about the library, but the pattern: short access, rotating refresh, reuse detection to catch theft, and storage in HttpOnly cookies with CSRF protection. Convenient to use, and revocable when needed.