Authentication server validates login, issues token, and grants access to three apps

JWT Security: The Tiny Token That Can Give an Attacker Access to Your Entire Account

spyboy's avatarPosted by

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:

HS256
HS384
HS512
RS256
RS384
RS512
ES256

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 → signs
Public 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 none everywhere.”

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:

Production
Staging
Development
Testing

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:

iss
sub
aud
exp
nbf
iat
jti

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 A
Service B
Service 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:

HttpOnly
Secure
SameSite

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 logs
SIEM
Cloud logging
Debugging systems
Error 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 quickly
Refresh 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 → invalidated
Token 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:

Passwords
API secrets
Private keys
Credit card information
Authentication 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:

Header
Payload
Claims
Expiration
Issuer
Audience
Algorithm

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:

iss
aud
exp
nbf

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 A
Account 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 nbf when used
  • Validate iss
  • Validate aud
  • Use jti where 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 → retired
Key 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

FeatureJWTServer-Side Session
StateOften client-heldServer-held
RevocationMore complicatedUsually easier
ScalingCan be convenientRequires shared session storage
Token contentsClaimsUsually just an identifier
Theft impactPotentially highPotentially high
ComplexityEasy to misuseOften 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.

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.