You log into a website.
The server gives your browser a token.
Your browser sends that token with future requests.
Everything works normally.
But what if that tiny token contains the information that tells the server who you are, what you’re allowed to do, and when your session expires?
And what if the application trusts that token more than it should?
A badly implemented JWT system can turn a small authentication mistake into:
- Account takeover
- Privilege escalation
- Authentication bypass
- Session impersonation
- Long-lived unauthorized access
This is why JSON Web Tokens (JWTs) deserve much more attention than simply:
“It’s just a token.”
What Is a JWT?
JWT stands for JSON Web Token.
It’s a compact format commonly used to transmit claims between systems.
A JWT often looks like this:
xxxxx.yyyyy.zzzzz
It has three components:
HEADER.PAYLOAD.SIGNATURE
For example:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjMiLCJyb2xlIjoidXNlciJ9.signature
The three sections are:
Header
Describes how the token is processed.
Payload
Contains claims.
Signature
Allows the recipient to verify that the token wasn’t modified.
The Important Part: JWT Payloads Are Not Secret
One of the biggest misconceptions about JWTs is:
“The payload is encrypted.”
Usually, it isn’t.
A normal signed JWT is encoded, not encrypted.
That means someone holding the token can generally decode its header and payload.
For example, a payload could contain:
{ "sub": "12345", "username": "alice", "role": "user"}
Anyone who possesses the token may be able to read those claims.
The signature protects integrity.
It does not automatically provide confidentiality.
So don’t put secrets inside an ordinary JWT payload.
JWT Authentication Flow
A simplified authentication flow looks like:
User ↓Login ↓Application ↓JWT issued ↓Browser stores token ↓Browser sends token ↓Server verifies JWT ↓Request authorized
For example:
Authorization: Bearer <token>
The server validates the token before processing the request.
Where JWT Security Goes Wrong
JWT itself isn’t necessarily the problem.
The problem is usually how developers implement it.
Common mistakes include:
- Accepting insecure algorithms
- Failing to validate claims
- Trusting user-controlled claims
- Using weak signing secrets
- Excessively long expiration times
- Poor key management
- Incorrect key selection
- Confusing authentication with authorization
- Mishandling refresh tokens
- Storing tokens insecurely
And some vulnerabilities occur because developers assume:
“If the token has a signature, everything inside it must be trustworthy.”
That’s not enough.
JWT Authentication vs Authorization
This distinction is extremely important.
Authentication asks:
Who are you?
Authorization asks:
What are you allowed to do?
A token might correctly identify:
{ "sub": "12345"}
but the application still needs to determine whether that user can:
DELETE /users/999
A valid JWT does not automatically mean:
“You can perform every action.”
The alg Claim
JWT headers commonly contain an algorithm field:
{ "alg": "HS256", "typ": "JWT"}
Different signing algorithms exist.
Common examples include:
HS256HS384HS512RS256RS384RS512ES256
The security problem isn’t simply:
“Which algorithm is used?”
It’s whether the application correctly enforces the algorithm it expects.
The server should not blindly trust attacker-controlled algorithm metadata.
Algorithm Confusion
Imagine a system was designed around asymmetric cryptography:
Private key → signsPublic key → verifies
But the application incorrectly allows a token to be interpreted using a symmetric algorithm where a shared secret is expected.
That can create an algorithm confusion vulnerability if key handling and algorithm validation are also flawed.
The important principle is:
The server must explicitly define which algorithms are acceptable.
Don’t allow the token itself to decide how authentication should work.
The none Algorithm Myth
You may see old JWT tutorials discussing:
"alg": "none"
Historically, vulnerabilities existed where applications incorrectly accepted unsigned JWTs.
Modern JWT libraries generally protect against this when correctly configured.
But old vulnerable implementations and custom JWT code can still create problems.
The lesson isn’t:
“Try
noneeverywhere.”
The lesson is:
Never assume the server will safely enforce cryptographic requirements. Test only within authorized scope.
Weak JWT Secrets
Symmetric algorithms such as HS256 use a shared secret.
Conceptually:
Secret ↓Signing ↓JWT
The server later uses the same secret to verify the signature.
If the secret is weak or predictable, attackers may potentially recover it through offline guessing.
For example, a terrible secret would be something like:
secret123
A production authentication secret should be:
- Random
- High entropy
- Long enough for the chosen algorithm
- Properly protected
- Rotatable
Why Secret Reuse Is Dangerous
Imagine a company uses the same JWT secret for:
ProductionStagingDevelopmentTesting
That’s a terrible separation strategy.
If the secret leaks from a less-protected environment, it can potentially affect production.
Use separate cryptographic keys for separate environments and security domains.
JWT Expiration
JWTs often include:
{ "exp": 1790000000}
The exp claim represents expiration time.
But simply including exp isn’t enough.
The application needs to actually validate it.
Other useful registered claims include:
isssubaudexpnbfiatjti
The iss Claim
iss means:
Issuer
It identifies who issued the token.
A server should validate the expected issuer when the application relies on it.
Otherwise, a token issued by an unexpected authority might potentially be accepted.
The aud Claim
aud means:
Audience
It identifies who the token is intended for.
This becomes especially important in systems with multiple services.
Imagine:
Authentication Service ↓JWT ↓Service AService BService C
A token intended for Service A shouldn’t automatically become a credential for every service.
Audience validation can help enforce that boundary.
The nbf Claim
nbf means:
Not Before
It can specify when a token becomes valid.
For example:
{ "nbf": 1790000000}
The application should validate this claim when it relies on it.
The jti Claim
jti is a token identifier.
It can be useful for:
- Token tracking
- Revocation systems
- Replay detection
- Incident response
JWTs are often described as stateless authentication, but real-world systems sometimes need additional infrastructure to revoke or track tokens.
The Biggest JWT Problem: Tokens Are Bearer Credentials
A bearer token effectively means:
Whoever possesses this token may be able to use it.
That’s why token theft is serious.
If an attacker obtains a valid session token, they may not need the user’s password.
They may simply send the token to the application.
Conceptually:
Password ↓Login ↓JWT ↓JWT stolen ↓Attacker ↓Authenticated requests
This is why protecting the token is as important as protecting the password.
Where JWTs Can Leak
Tokens can accidentally appear in:
- URLs
- Browser history
- Logs
- Analytics
- Error reports
- Screenshots
- Source code
- Git repositories
- Referrer headers
- Third-party integrations
- Browser extensions
- Client-side storage
A particularly dangerous pattern is:
https://example.com/reset?token=JWT
Tokens in URLs can end up in places developers didn’t expect.
JWT in Local Storage
A common browser implementation stores JWTs in:
localStorage
This can be convenient.
But if the application has a serious XSS vulnerability, JavaScript running in the origin may potentially access the token.
So:
XSS ↓Token access ↓Session compromise
is one possible attack chain.
There isn’t one universal storage choice that’s perfect for every application, but token exposure should be treated as a major design concern.
HttpOnly Cookies
Another approach is to store authentication state in cookies configured with security attributes such as:
HttpOnlySecureSameSite
HttpOnly prevents normal JavaScript from directly reading the cookie.
Secure restricts transmission to HTTPS.
SameSite helps control cross-site cookie sending.
But cookies don’t magically eliminate every authentication vulnerability.
Developers still need to consider:
- CSRF
- session fixation
- token expiration
- logout
- refresh
- cookie scope
JWT Theft Through XSS
Imagine a site stores a bearer token in a JavaScript-accessible location.
Then the application has an XSS vulnerability.
The chain could become:
XSS ↓JavaScript execution ↓Token exposure ↓Token replay ↓Account compromise
This is one reason authentication architecture and frontend security cannot be treated as completely separate problems.
JWT Theft Through Logs
Developers sometimes log complete HTTP requests during debugging.
For example:
Authorization: Bearer eyJ...
That token could end up in:
Application logsSIEMCloud loggingDebugging systemsError trackers
Now a person who can access logs may have access to active credentials.
Never treat authentication tokens as harmless debugging data.
JWTs and Password Reset Links
Password-reset mechanisms require particularly careful token handling.
A reset token should:
- Be unpredictable
- Expire quickly
- Be single-use where appropriate
- Be protected from leakage
- Be invalidated after successful use
A long-lived reusable authentication JWT should not casually double as a password-reset credential.
Keep security-sensitive token purposes separated.
Refresh Tokens
Many applications use:
Access Token+Refresh Token
The access token may be short-lived.
The refresh token allows obtaining a new access token.
Conceptually:
Login ↓Access Token ──→ API │ └── expires quicklyRefresh Token │ ↓Authentication Server │ ↓New Access Token
This can reduce the impact of an exposed short-lived access token.
But now the refresh token becomes extremely sensitive.
Refresh Token Rotation
A stronger architecture may rotate refresh tokens.
For example:
Refresh Token A ↓New Access Token ↓Refresh Token B
Then:
Token A → invalidatedToken B → active
If an old refresh token is reused unexpectedly, the system may detect possible token theft and revoke the token family.
The exact implementation depends on the authentication architecture.
JWT Revocation Is Complicated
Traditional server-side sessions are relatively easy to invalidate:
Session ID ↓Server-side session store ↓Delete session
JWTs are often self-contained.
Once issued:
JWT ↓Valid until expiration
unless the server maintains some revocation mechanism.
This creates a trade-off.
Long expiration:
Convenient+Less frequent reauthentication-More dangerous if stolen
Short expiration:
Less exposure window+Better containment-Requires refresh/re-authentication
Don’t Put Sensitive Data in JWT Payloads
Avoid storing information such as:
PasswordsAPI secretsPrivate keysCredit card informationAuthentication secrets
A signed JWT payload is generally readable by whoever has the token.
For example:
{ "username": "alice", "role": "admin", "internal_secret": "..."}
The signature protects integrity.
It doesn’t make that payload confidential.
JWT Privilege Escalation
One of the most important authorization mistakes is trusting claims without properly enforcing them.
Imagine:
{ "sub": "123", "role": "user"}
The application must verify the signature and then make sure the authorization system correctly uses the trusted claim.
A dangerous design is one where role information can somehow be altered without cryptographic verification.
The general security principle:
Never trust security-sensitive claims until the token has been properly validated.
JWT and IDOR
JWT security problems can also interact with IDOR.
Imagine a token identifies:
user_id = 100
but an API accepts:
GET /api/users/101
The server still needs to enforce authorization.
A valid JWT for User 100 doesn’t mean:
User 100 → access User 101
Authentication and object-level authorization are separate controls.
How Bug Bounty Researchers Analyze JWTs
If you’re testing a system within an authorized bug bounty scope, JWT analysis can start with passive inspection.
Look for:
Authorization: Bearer ...
or authentication cookies containing JWT-like values.
Decode the token locally and inspect:
HeaderPayloadClaimsExpirationIssuerAudienceAlgorithm
Do not expose real tokens in public reports.
What Researchers Look For
During authorized testing, security researchers may investigate:
Algorithm enforcement
Does the server strictly enforce the intended signing algorithm?
Claim validation
Are:
issaudexpnbf
properly checked?
Authorization
Does changing legitimate application state actually change server-side permissions?
Token lifetime
Are highly privileged tokens unnecessarily long-lived?
Token leakage
Are tokens exposed through URLs, logs, or client-side mechanisms?
Key management
Are production keys properly protected?
A Safe JWT Testing Methodology
Use:
Your account+Your test data+Authorized target
Start by capturing your own token.
Decode it.
Document:
Algorithm:Issuer:Audience:Subject:Expiration:Roles:
Then test expected application behavior.
For example:
Normal user ↓Normal endpoint ↓Expected access
Then:
Normal user ↓Admin endpoint ↓403 / authorization denial
The goal is to verify that authorization actually works.
Don’t Test With Someone Else’s Token
Never obtain or reuse another person’s authentication token simply to prove a vulnerability.
A strong security report can usually be produced using:
Account AAccount B
both controlled by you.
This is especially important when testing:
- privilege escalation
- IDOR
- session handling
- refresh tokens
- authorization boundaries
JWT Security Checklist for Developers
Token Design
- Use a well-maintained JWT library
- Explicitly allow expected algorithms
- Use strong cryptographic keys
- Separate keys by environment
- Keep tokens short-lived when appropriate
- Validate required claims
Claims
- Validate
exp - Validate
nbfwhen used - Validate
iss - Validate
aud - Use
jtiwhere appropriate
Storage
- Protect browser tokens from unnecessary JavaScript exposure
- Avoid putting tokens in URLs
- Don’t log bearer tokens
- Protect refresh tokens carefully
Authorization
- Don’t confuse authentication with authorization
- Enforce permissions server-side
- Don’t blindly trust client-controlled identity information
- Test cross-user access
Operations
- Have a key rotation strategy
- Have an incident-response plan
- Revoke compromised refresh-token families where applicable
- Monitor suspicious token activity
What Happens If a JWT Signing Key Leaks?
This is one of the worst scenarios.
If an attacker obtains the signing key for a symmetric JWT system, they may potentially be able to create tokens that the application accepts.
Conceptually:
Signing Key Leaked ↓Attacker creates token ↓Server verifies signature ↓Server accepts token
The impact can be enormous.
This is why signing keys belong in secure secret-management systems, not:
GitHub repositories
or:
.env files committed to source control
or:
public frontend code
Key Rotation Matters
Suppose:
Key A ↓Millions of tokens
Then Key A becomes compromised.
A mature system needs a strategy for:
Key A → retiredKey B → active
and must determine how existing tokens are handled.
Key rotation isn’t just an operational task.
It’s part of the authentication security model.
JWT Doesn’t Automatically Mean “Stateless and Secure”
You may hear:
“JWT makes authentication stateless.”
Sometimes.
But real applications often still need server-side state for:
- refresh-token rotation
- revocation
- logout
- device management
- suspicious-session detection
- key rotation
- session tracking
JWT is a format.
It isn’t a complete authentication architecture.
JWT vs Traditional Sessions
| Feature | JWT | Server-Side Session |
|---|---|---|
| State | Often client-held | Server-held |
| Revocation | More complicated | Usually easier |
| Scaling | Can be convenient | Requires shared session storage |
| Token contents | Claims | Usually just an identifier |
| Theft impact | Potentially high | Potentially high |
| Complexity | Easy to misuse | Often simpler to reason about |
The correct choice depends on the application.
Using JWT just because:
“Everyone uses JWT”
isn’t a security strategy.
The Most Dangerous JWT Mistakes
If you remember only a few things, remember these:
1. Never treat JWT payloads as encrypted.
They’re commonly readable.
2. Never let the token choose arbitrary cryptographic behavior.
Algorithms must be explicitly controlled.
3. Never use weak signing secrets.
Authentication depends on them.
4. Never forget claim validation.
Expiration and audience checks matter.
5. Never trust JWT claims as a substitute for authorization.
A valid identity doesn’t grant unlimited access.
6. Never expose tokens unnecessarily.
A bearer token is a credential.
7. Never make tokens live forever.
Shorter lifetimes reduce the window of opportunity after theft.
Final Takeaway
JWTs look simple:
HEADER.PAYLOAD.SIGNATURE
But behind those three pieces is an entire authentication system.
When implemented correctly, JWTs can work extremely well.
When implemented incorrectly, a tiny token can become the equivalent of a digital master key.
The most important lesson is:
A JWT is not security by itself.
The real security comes from:
Strong cryptography+Strict algorithm enforcement+Correct claim validation+Secure token storage+Short lifetimes+Proper key management+Server-side authorization+Good incident response
And for security researchers, the most valuable question isn’t:
“Can I decode this JWT?”
Anyone can decode a JWT.
The better question is:
“What does the server actually trust after it verifies this token?”
Because that’s where authentication vulnerabilities begin.
The token isn’t the security boundary. How the server validates and uses it is.
Think Like an Attacker. Secure Like a Pro.
Discover more from Spyboy blog
Subscribe to get the latest posts sent to your email.
