Axonix Tools
JWTs Explained: No More Auth Headaches
Back to Insights
SecurityAuthenticationWeb Development

JWTs Explained: No More Auth Headaches

7 min read
Reviewed:

JSON Web Tokens are the standard for stateless authentication. But how do they actually work? We break down the header, payload, and signature so you can implement auth securely.

Authentication creates friction.

Sessions, cookies, local storage, databases... there are a million moving parts.

Usually, you just install next-auth or passport.js and move on. But if you don't understand the underlying mechanism, you are opening yourself up to massive security holes.

The standard today is the JWT (JSON Web Token). It looks like a long string of gibberish: eyJhbGci....

But it's not encrypted (usually). It's just encoded. Anyone can read it.

Anatomy of a Token

A JWT has three parts, separated by dots:

1. Header (Red)

{"alg": "HS256", "typ": "JWT"} This tells the server: "I am a JWT, and I was signed using HMAC SHA-256."

2. Payload (Purple)

{"userId": "123", "role": "admin", "exp": 1735689600} This is the data. Critical Warning: Since this is just Base64 encoded, never put secrets like passwords in here. Anyone with the token can decode this part. It verifies identity, it does not hide information.

3. Signature (Blue)

This is the magic. The server takes the Header, the Payload, and a Secret Key (only known to the server) and mashes them together mathematically.

If a hacker tries to change "role": "user" to "role": "admin" in the payload, the signature won't match anymore. The server will reject it.

Statelessness: The Big Win

Why do we use these?

Scalability.

With traditional sessions, the server has to check the database for every single request to see if "SessionID 123" is logged in. That's slow.

With JWTs, the server just checks the math of the signature. It requires zero database lookups. This means you can have 10,000 users hitting your API, and your auth layer won't even blink.

Debugging Tokens

Since JWTs are just Base64 strings, you can inspect them.

We built a Token Generator & Debugger that lets you paste a token and see exactly what's inside. You can also generate random secure keys for signing your own tokens.

Common Vulnerabilities (How to Get Hacked)

If you strictly follow the "Header.Payload.Signature" model, you are mostly safe. But developers make mistakes. Here are the two most common hacks:

The "None" Algorithm Attack

The header contains "alg": "HS256". In the early days of JWTs, you could change this to "alg": "none". Some backend libraries would see this and say, "Oh, the algorithm is 'none'? Okay, I won't check the signature." Result: The hacker bypasses authentication entirely. Fix: Always whitelist the algorithms you accept on the server (e.g., strictly HS256 or RS256).

The Secret Key Brute Force

If your secret key is secret123, a hacker can brute-force it in seconds using a tool like hashcat. Once they have your key, they can sign their own tokens. Fix: Use a cryptographically strong, random string (at least 256-bit).

Access vs. Refresh Tokens: The Golden Pattern

You might ask: "If tokens are stateless, how do I log a user out?" You can't really "delete" a JWT that is on someone else's computer.

This is why we use two tokens:

  1. Access Token (Short-lived): Expires in 15 minutes. Used for API calls.
  2. Refresh Token (Long-lived): Expires in 7 days. Stored securely (HttpOnly cookie) and used ONLY to get new Access Tokens.

When you "logout", you delete the Refresh Token record in your database. The user's Access Token will work for 5 more minutes, but once it expires, they can't get a new one.

Security high-performing Practices Checklist

  1. Short Expiry: Access tokens should die quickly.
  2. HTTPS Only: If you send a token over HTTP, I can steal it in a coffee shop.
  3. HttpOnly Cookies: Store tokens in cookies that JavaScript cannot access. This prevents XSS attacks (malicious scripts) from stealing your user's identity.
  4. Validate Algorithms: Explicitly set algorithms=['HS256'] in your verification function.

Conclusion

Auth is hard, but JWTs make it portable and efficient. Understand the signature, respect the secret key, and never trust client data.

👉 Inspect Your Tokens: Token Generator & Debugger

Real-world JWT implementation mistakes

I've audited authentication systems at several companies and the same mistakes show up everywhere:

Storing JWTs in localStorage. This is the most common vulnerability I find. localStorage is accessible to any JavaScript running on the page. If an XSS vulnerability exists (and it often does), the attacker can read the token and impersonate the user. Use HttpOnly cookies instead. They're invisible to JavaScript.

Not rotating refresh tokens. Some implementations issue a refresh token and never invalidate it. If a refresh token is compromised, the attacker can generate new access tokens indefinitely. Rotate refresh tokens on every use. The old one becomes invalid, and a new one is issued with each refresh request.

Including sensitive data in the payload. Remember, the payload is just Base64. Anyone can decode it. I've seen JWTs containing email addresses, phone numbers, internal user IDs, and even password reset tokens. The payload should contain only what the client needs to display (user ID, role, display name). Everything else stays on the server.

Ignoring token size. JWTs grow with every claim you add. A token with a few claims is fine. A token with a shopping cart, user preferences, and a full permission matrix can exceed 8KB. That's bigger than many servers' default header size limits, and it bloats every single request. Keep tokens lean.

JWTs vs sessions: when to use which

JWTs aren't always the right choice. Here's when I use each:

Use sessions when: you have a monolithic app, you need server-side logout control, your user data changes frequently, or you're building a simple app where a Redis-backed session store is easy to manage.

Use JWTs when: you're building an API that serves multiple frontends (web, mobile, third-party), you need horizontal scaling without shared session storage, you're implementing microservices that need to verify authentication independently, or you're integrating with third-party auth providers.

The choice isn't about which is "better." It's about which fits your architecture. Many production systems use both: session cookies for the web app and JWTs for API access from mobile clients.

Debugging JWT issues in production

When JWT authentication breaks in production, it's usually one of these:

Clock skew. The token's exp claim uses a Unix timestamp. If your server's clock is even a few minutes off, valid tokens get rejected. Use NTP to keep your servers synchronized, and add a small tolerance (30-60 seconds) to your token validation.

Algorithm mismatch. The token was signed with RS256, but your verification code defaults to HS256. Always explicitly specify the algorithm when verifying tokens.

Secret rotation. You changed your signing secret but some servers still have the old one cached. In a multi-server deployment, rotating secrets requires deploying all servers within the token's expiry window.

Audience mismatch. If your token specifies an audience (aud claim), your verification code must check it. A token issued for api.example.com should not be accepted by admin.example.com.

When debugging, decode the token first using the Token Debugger and check the header and payload for these common issues. Nine times out of ten, the problem is visible in the decoded token.

JWTs in practice: what I actually recommend

After implementing JWT authentication in several production systems, here is what I tell other developers:

Keep tokens short-lived. Fifteen minutes for access tokens is a good default. Short enough that a leaked token has limited value. Long enough that the user does not get logged out mid-action.

Store refresh tokens in HttpOnly cookies. Not localStorage. Not sessionStorage. Cookies that JavaScript cannot access are immune to XSS attacks. Yes, you need to handle CSRF. But CSRF is easier to defend against than XSS.

Validate on every request. Do not cache validation results. The token might have been revoked. The user might have been deactivated. The claims might have changed. Check the signature and claims on every protected request.

Use short, descriptive claim names. sub for subject, iss for issuer, exp for expiration, aud for audience. These are standard claims that libraries know how to validate automatically. Custom claims should be concise and documented.

Log token validation failures. When a request fails authentication, log the reason. Expired token. Invalid signature. Missing claim. These logs help you debug integration issues with clients and identify potential security threats.

JWTs are not perfect. But they solve a real problem (stateless authentication across distributed systems) in a way that works. Understand the tradeoffs, follow the security checklist, and you will be fine.

AT

Axonix Team

Technical Writers & Contributors

The collective editorial team behind Axonix Tools. We write practical tutorials, developer guides, and tool documentation focused on web development, design...

Share this article

Discover More

View all articles

Need a tool for this workflow?

Axonix provides 100+ browser-based tools for practical development, design, file, and productivity tasks.

Explore Our Tools