JWT Tokens Explained: What They Are and Why Developers Love (And Hate) Them
I was debugging an authentication issue last week. The API kept returning "Invalid token," but the token looked fine. After 3 hours of frustration, I finally decoded it and saw the problem: the expiration time was set to a timestamp in the past. The token had expired, but the error message didn't say that.
That's when I realized: JWT tokens are powerful, but they're also confusing. Let me explain what they actually are, how they work, and the mistakes I've seen developers make (including myself).
What Is a JWT Token? (In Plain English)
JWT stands for JSON Web Token. Think of it as a digital ID card that proves who you are. But unlike a physical ID card, a JWT contains information about you and can be verified without contacting the server that issued it.
Here's the key insight: JWTs are self-contained. All the information needed to verify them is in the token itself (or can be derived from it). This makes them perfect for distributed systems where you don't want to check a database on every request.
Real-World Analogy:
Imagine a concert ticket. It has your seat number, show time, and a barcode. The barcode can be scanned to verify it's real, but the ticket itself contains all the information. You don't need to call the box office to check if it's valid - the ticket is self-contained. That's how JWTs work.
The Three Parts of a JWT
Every JWT has three parts, separated by dots. Let me break down what each part does:
1. Header
The header tells you what algorithm was used to sign the token. It's usually something like:
{
"alg": "HS256",
"typ": "JWT"
}
"alg" is the algorithm (HS256, RS256, etc.), and "typ" is always "JWT". This is base64-encoded, so it looks like gibberish in the actual token.
2. Payload
This is where the actual data lives. It contains "claims" - information about the user and the token itself. Common claims include:
- sub (subject): The user ID
- exp (expiration): When the token expires (Unix timestamp)
- iat (issued at): When the token was created
- name: User's name
- email: User's email
You can add custom claims too. I've seen tokens with user roles, permissions, even favorite color (though that's probably overkill).
3. Signature
The signature is what makes the token secure. It's created by taking the header and payload, combining them, and signing them with a secret key (for HS256) or private key (for RS256).
When someone receives a JWT, they can verify the signature to ensure the token hasn't been tampered with. If someone changes the payload, the signature won't match, and the token will be rejected.
How JWTs Actually Work
Here's the flow I see in most applications:
Step 1: User Logs In
User sends username and password to your server. Server verifies credentials (checks database, validates password hash, etc.).
Step 2: Server Creates JWT
Server creates a JWT with user information in the payload. It signs the token with a secret key and sends it back to the client.
Step 3: Client Stores Token
Client (browser, mobile app, etc.) stores the token. Usually in localStorage, sessionStorage, or a cookie. Each has trade-offs.
Step 4: Client Sends Token with Requests
Every API request includes the JWT in the Authorization header: "Bearer <token>". The server receives it and verifies the signature.
Step 5: Server Verifies Token
Server checks the signature (makes sure it's valid), checks expiration (makes sure it hasn't expired), and extracts user information from the payload. No database lookup needed.
Why Developers Use JWTs
JWTs solve real problems:
Stateless Authentication
You don't need to store sessions in a database or Redis. The token contains everything. This makes scaling easier - you can add more servers without worrying about session storage.
Microservices Friendly
In a microservices architecture, service A can verify a JWT without talking to service B. Each service can independently verify tokens. This reduces inter-service communication.
Mobile App Support
Mobile apps work well with JWTs. The app stores the token and sends it with every request. No need for complex session management.
Common JWT Mistakes (And How to Avoid Them)
I've made these mistakes. You probably will too. Here's how to avoid them:
Mistake 1: Storing Sensitive Data in the Payload
JWTs are base64-encoded, not encrypted. Anyone can decode them and read the payload. I've seen developers store passwords, credit card numbers, and API keys in JWTs. Don't do this.
Only store non-sensitive information: user ID, email, roles. Never store secrets.
Mistake 2: Using Weak Algorithms
"none" algorithm means no signature. Anyone can create a valid token. Always use HS256 (for symmetric keys) or RS256 (for asymmetric keys). Never use "none".
Mistake 3: Not Setting Expiration
Tokens without expiration never expire. If someone steals a token, they can use it forever. Always set an "exp" claim. I usually set it to 1 hour for access tokens, 7 days for refresh tokens.
Mistake 4: Not Handling Expiration Gracefully
When a token expires, the API should return a clear error. I've seen APIs return "Invalid token" for expired tokens, which is confusing. Return "Token expired" so the client knows to refresh it.
How to Debug JWT Issues
When JWT authentication fails, here's how I debug it:
Step 1: Decode the Token
Use our JWT Debugger to decode the token. Paste it in and see the header and payload. This immediately shows you:
- What algorithm was used
- What claims are in the payload
- When the token expires
- When it was issued
Try It Now:
Debug JWT tokens instantly with our JWT Debugger. Paste any JWT token and see the decoded header, payload, and signature information.
Step 2: Check Expiration
Look at the "exp" claim. Convert the Unix timestamp to a date and see if it's in the past. If it is, the token expired. That's your problem.
Step 3: Verify the Algorithm
Check the "alg" in the header. Make sure your server is using the same algorithm to verify. Mismatched algorithms cause verification failures.
Step 4: Check the Signature
If expiration and algorithm are fine, the signature might be wrong. This usually means:
- The secret key changed
- The token was tampered with
- You're using the wrong secret key to verify
When to Use JWTs (And When Not To)
JWTs aren't always the right choice:
Use JWTs When:
- You need stateless authentication
- You're building a microservices architecture
- You need to share authentication across multiple domains
- You're building mobile apps
Don't Use JWTs When:
- You need to revoke tokens immediately (JWTs can't be revoked until expiration)
- You're storing sensitive data (JWTs aren't encrypted)
- You need simple session management (traditional sessions might be easier)
The Bottom Line
JWTs are powerful tools for authentication, but they require understanding. Know what's in your tokens, set proper expiration, use strong algorithms, and always verify signatures.
When debugging JWT issues, start by decoding the token. Our JWT Debugger makes this instant - paste the token and see exactly what's inside. It's saved me hours of debugging time.
Debug Your JWTs:
Use our JWT Debugger to decode and analyze JWT tokens. See header, payload, expiration, and signature information instantly.