Learn how JWT security vulnerabilities and misconfigurations occur, including algorithm confusion, weak secrets, alg: none, kid, jku, JWKS, claim validation, token replay, refresh token security, authorization flaws, key management, and secure JWT implementation practices.

JSON Web Tokens (JWTs) have become a fundamental part of authentication and authorization in modern web applications.

They are widely used in REST APIs, mobile applications, Single Sign-On systems, microservice architectures, and OAuth/OpenID Connect environments.

However, the widespread adoption of JWT does not mean that JWT implementations are secure by default.

Weak signing secrets, incorrect algorithm handling, missing claim validation, excessive token lifetimes, insecure token storage, and broken authorization logic can turn an otherwise standard JWT implementation into a serious security vulnerability.

This guide explores JWT security from both a defensive and penetration-testing perspective, including how JWT works, common implementation mistakes, attack surfaces, token lifecycle problems, key management issues, and recommended security practices.

Ethical Use Notice: This article is intended for security education and authorized security testing. Only test systems that you own or have explicit permission to assess.


1. What Is JWT?

JWT stands for:

JSON Web Token

It is a compact format designed to securely transmit claims between different parties.

A typical JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0Iiwicm9sZSI6InVzZXIiLCJleHAiOjE3MDAwMDAwMDB9.signature

A JWT commonly consists of three sections:

HEADER.PAYLOAD.SIGNATURE

Conceptually:

Header
   ↓
Payload
   ↓
Signature

Each section is separated using a period:

xxxxx.yyyyy.zzzzz

The Header and Payload are normally encoded using Base64URL.

One of the most important things to understand is:

The Header and Payload of a signed JWT are normally encoded, not encrypted.

Anyone who obtains the token can usually decode these sections.

Therefore, sensitive information such as passwords, private keys, API secrets, or payment information should not be placed inside a normal signed JWT payload.


2. JWT Header

The JWT Header describes metadata related to the token.

A common header looks like:

{
  "alg": "HS256",
  "typ": "JWT"
}

The alg parameter specifies the signing algorithm.

Examples include:

HS256
RS256
ES256

The typ parameter commonly indicates the token type:

JWT

After Base64URL encoding, the header may look similar to:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

This value can easily be decoded.

Therefore, security must never depend on hiding information inside the JWT Header.


3. JWT Payload

The Payload contains the claims associated with the token.

For example:

{
  "sub": "1234",
  "username": "kutay",
  "role": "user",
  "iat": 1760000000,
  "exp": 1760003600
}

JWT defines several registered claims.

Claim Meaning
iss Issuer
sub Subject
aud Audience
exp Expiration Time
nbf Not Before
iat Issued At
jti JWT ID

Applications can also define custom claims.

For example:

{
  "user_id": 42,
  "role": "admin",
  "permissions": [
    "read",
    "write"
  ]
}

These custom claims often become extremely important during security assessments because applications may use them when making authorization decisions.


4. How JWT Signatures Work

Consider an HMAC SHA-256 JWT.

Conceptually, its signature can be generated using:

HMACSHA256(
    base64UrlEncode(header) + "." + base64UrlEncode(payload),
    secret
)

The important input is:

header.payload

The server signs this data using a secret.

When the JWT is later received, the server calculates the signature again and compares it against the signature supplied with the token.

Suppose the original payload contains:

{
  "role": "user"
}

If someone modifies it to:

{
  "role": "admin"
}

the original signature should no longer match.

A correctly implemented server rejects the modified token.

This integrity protection is one of the primary purposes of JWT signatures.


5. Encoding Is Not Encryption

One of the most common JWT misconceptions is assuming that Base64URL encoding provides confidentiality.

Consider:

eyJ1c2VybmFtZSI6Imt1dGF5Iiwicm9sZSI6InVzZXIifQ

After decoding:

{
  "username": "kutay",
  "role": "user"
}

The information is immediately visible.

Therefore, avoid placing information such as:

password
API secret
private key
credit card information
database credentials
internal secrets

inside ordinary signed JWT payloads.

The signature protects integrity and authenticity when correctly implemented.

It does not automatically provide confidentiality.


6. Understanding the JWT Attack Surface

When encountering JWT authentication during an authorized security assessment, several questions should immediately be considered:

  • Which signing algorithm is being used?
  • Is the signature actually verified?
  • Is the expected algorithm enforced server-side?
  • Does the token expire?
  • Is expiration actually validated?
  • Is the issuer validated?
  • Is the audience validated?
  • Are roles or permissions stored inside the token?
  • Are authorization decisions based entirely on token claims?
  • How are signing keys managed?
  • Can tokens be revoked?
  • Are refresh tokens used?
  • Can tokens intended for one service be reused against another?
  • Where are tokens stored?
  • Are tokens accidentally exposed through logs or URLs?

JWT security should therefore be examined as an entire trust model rather than simply as a cryptographic problem.


7. The alg: none Problem

One historically important JWT implementation flaw involves the none algorithm.

A JWT header could theoretically contain:

{
  "alg": "none",
  "typ": "JWT"
}

The payload could contain:

{
  "username": "kutay",
  "role": "admin"
}

The resulting structure may effectively become:

HEADER.PAYLOAD.

Some vulnerable or outdated implementations historically accepted unsigned tokens when the attacker changed the algorithm to:

none

The core security mistake is allowing an untrusted token to control whether cryptographic verification is required.

Secure Approach

The application should determine the expected algorithm independently.

Bad design:

Use whatever algorithm the token requests.

Better design:

This API accepts RS256 only.

Conceptually:

verify(
    token,
    publicKey,
    algorithms = ["RS256"]
)

Strict algorithm allowlisting is an important JWT security control.


8. Algorithm Confusion

Another historically important JWT vulnerability class is algorithm confusion.

Understanding the difference between symmetric and asymmetric signing algorithms is essential.

HS256

HS256 uses HMAC.

The same secret is used for signing and verification:

SECRET
   |
   +---- SIGN
   |
   +---- VERIFY

The secret therefore must remain confidential.

RS256

RS256 uses asymmetric cryptography.

Two keys exist:

Private Key
Public Key

The private key signs tokens:

PRIVATE KEY
     ↓
   SIGN
     ↓
    JWT

The public key verifies them:

JWT
 ↓
VERIFY
 ↓
PUBLIC KEY

The public key does not need to remain secret.

Problems have historically occurred when vulnerable libraries or applications failed to properly distinguish these algorithm families.

For example, an application designed for RS256 could incorrectly accept a token using HS256 and treat public-key material as an HMAC secret.

Prevention

The expected algorithm should be explicitly configured:

algorithms = ["RS256"]

The application should not dynamically trust the algorithm selected by an untrusted token.


9. Weak HMAC Secrets

With HMAC algorithms such as HS256, security depends heavily on the strength of the signing secret.

Examples of dangerously weak secrets include:

secret
password
123456
jwtsecret
admin
secret123
companyname

If an attacker obtains a legitimate JWT, weak HMAC secrets may be susceptible to offline guessing.

Conceptually:

Candidate Secret
       ↓
HMAC(header.payload)
       ↓
Generated Signature
       ↓
Compare
       ↓
JWT Signature

Because the verification process can potentially be performed offline, rate limiting on the authentication endpoint does not necessarily protect a weak signing secret.

Secure Approach

HMAC secrets should be:

  • cryptographically random,
  • sufficiently long,
  • high entropy,
  • unique to the environment,
  • securely generated,
  • securely stored.

Avoid secrets such as:

company2026
supersecret
jwt_password
production_secret

Signing secrets should also not be hard-coded into application source code.

Bad:

const JWT_SECRET = "secret123";

Secrets should instead be managed through an appropriate secrets-management solution.


10. Excessive Trust in Role Claims

Consider a JWT containing:

{
  "id": 15,
  "username": "kutay",
  "role": "user"
}

The backend may perform authorization based on:

role == admin?

A properly verified signature prevents users from simply modifying the claim.

However, another problem remains.

Suppose an administrator is demoted:

admin → user

Their previously issued JWT may still contain:

{
  "role": "admin"
}

If that token remains valid for several hours or days, the old privileges could potentially remain available.

This demonstrates an important JWT principle:

Cryptographically valid does not necessarily mean currently authorized.

Applications should consider whether sensitive authorization information should be checked against current server-side state.


11. Missing exp Claim

Consider:

{
  "sub": "123"
}

There is no:

exp

claim.

Without an appropriate expiration mechanism, the token may remain usable for an excessive amount of time.

This becomes especially dangerous when tokens leak through:

Application logs
Reverse proxy logs
Browser history
Monitoring platforms
Source code
Screenshots
Support tickets
Analytics systems

Shorter token lifetimes reduce the window in which stolen credentials remain useful.

For example:

{
  "iat": 1760000000,
  "exp": 1760000900
}

represents approximately a 15-minute lifetime.

The appropriate lifetime depends on the application's threat model and usability requirements.


12. exp Exists but Is Not Validated

Having an exp claim does not automatically make expiration enforcement secure.

Consider:

{
  "sub": "123",
  "exp": 1600000000
}

The token may already be expired.

If the backend only checks:

Is the signature valid?

without checking:

Has the token expired?

the token may still be accepted.

JWT validation therefore usually involves more than signature verification.

Depending on the architecture, validation may include:

Signature
Expiration
Not Before
Issuer
Audience
Token Type

13. nbf Validation

nbf means:

Not Before

For example:

{
  "nbf": 1760000000
}

The token should not be accepted before the specified timestamp.

If the application ignores nbf, a token may become usable earlier than intended.

This becomes particularly relevant for time-restricted authorization workflows.


14. Issuer (iss) Validation

The iss claim identifies the entity that issued the token.

Example:

{
  "iss": "https://auth.example.com"
}

A backend should not necessarily accept a token merely because its cryptographic signature can be verified.

It may also need to verify that the token came from the expected issuer.

Conceptually:

Signature valid?
      ↓
Issuer expected?
      ↓
Continue

If the application expects:

https://auth.example.com

then tokens from an unrelated issuer should not automatically be trusted.

This becomes especially important in environments involving multiple identity providers.


15. Audience (aud) Validation

The aud claim identifies the intended recipient of a token.

For example:

{
  "aud": "payments-api"
}

Consider the following microservice architecture:

Authentication Server
         |
   -----------------
   |       |       |
Profile  Orders  Payments

A token intended for:

profile-api

should not automatically be accepted by:

payments-api

Failing to validate the audience can enable cross-service token reuse.

Each service should validate that the token was actually issued for that service or an explicitly trusted audience.


16. Token Replay

JWTs are commonly used in stateless authentication architectures.

This provides scalability advantages but introduces an important consideration.

If a valid access token is stolen, it may potentially be reused until it expires.

This is known as:

Token Replay

For example, an attacker who obtains:

Authorization: Bearer <TOKEN>

may attempt to reuse the same token.

This demonstrates why JWT security is not simply about protecting the signing key.

Token confidentiality is also extremely important.


17. JWTs in URLs

Consider:

https://example.com/profile?token=eyJhbGciOi...

Placing authentication tokens inside URLs is dangerous.

URLs can appear in:

Browser history
Reverse proxy logs
Web server logs
Analytics systems
Monitoring platforms
Screenshots
Referrer information
Support systems

For APIs, bearer tokens are commonly transmitted through the HTTP Authorization header:

Authorization: Bearer <TOKEN>

Authentication credentials should generally not be placed inside query strings.


18. JWT and LocalStorage

Single-page applications sometimes store access tokens using:

localStorage.setItem("token", token);

The important security consideration is that JavaScript executing within the application's origin can access LocalStorage.

If the application contains an XSS vulnerability, malicious JavaScript may potentially access the stored token.

Conceptually:

localStorage.getItem("token");

Therefore, the combination of:

XSS
+
JavaScript-accessible authentication tokens

can significantly increase the impact of an XSS vulnerability.

Cookie-based approaches may instead use controls such as:

HttpOnly
Secure
SameSite

However, cookies introduce their own security considerations, including CSRF protections.

There is no universal storage mechanism that automatically solves every authentication threat.


19. HttpOnly Cookies

Consider:

Set-Cookie: access_token=TOKEN; HttpOnly; Secure; SameSite=Lax

The HttpOnly attribute prevents ordinary client-side JavaScript from directly reading the cookie.

This can make some token-stealing attacks more difficult.

However:

HttpOnly ≠ XSS protection

An attacker with JavaScript execution inside the application may still perform authenticated actions through the victim's browser.

Therefore, XSS remains a serious vulnerability even when authentication cookies are protected using HttpOnly.


20. Refresh Token Security

Modern authentication architectures frequently use:

Access Token
+
Refresh Token

Access tokens are normally relatively short-lived.

Refresh tokens may remain valid for much longer.

The workflow may look like:

LOGIN
  ↓
ACCESS TOKEN
+
REFRESH TOKEN
  ↓
Access Token Expires
  ↓
Refresh Endpoint
  ↓
NEW ACCESS TOKEN

Because refresh tokens can be used to obtain new access tokens, they are high-value credentials.

Refresh tokens should therefore be:

  • securely stored,
  • revocable,
  • protected from unnecessary exposure,
  • rotated where appropriate,
  • monitored for suspicious reuse.

21. Refresh Token Rotation

Refresh token rotation can reduce the impact of refresh-token theft.

Suppose the client initially receives:

Refresh Token A

After using it:

A → invalid
B → new refresh token

The next refresh becomes:

B → invalid
C → new refresh token

If:

A

is later reused, the server knows that an already-consumed credential has appeared again.

Depending on the architecture, this may trigger:

Token family revocation
Session termination
Security logging
User notification
Reauthentication

This technique is often referred to as refresh token reuse detection.


22. Does Logout Actually Revoke the Token?

Stateless JWT systems introduce an interesting logout problem.

The frontend may simply delete the token:

Delete local token

But the JWT itself may remain cryptographically valid.

Consider:

1. User authenticates.
2. Server issues JWT.
3. JWT is stolen.
4. User logs out.
5. Attacker continues using the stolen JWT.

If the backend does not maintain revocation state, the token may continue working until:

exp

is reached.

Depending on the application's security requirements, possible strategies include:

Short-lived access tokens
Refresh token revocation
Session storage
Token versioning
Revocation lists

There is a trade-off between fully stateless authentication and immediate revocation capabilities.


23. The jti Claim

jti stands for:

JWT ID

It can provide a unique identifier for a particular token.

Example:

{
  "jti": "550e8400-e29b-41d4-a716-446655440000"
}

The identifier may be useful for:

Revocation
Audit logging
Replay detection
Session tracking

For example, a backend could maintain:

revoked_jti

records.

When a JWT is received:

JWT
 ↓
Extract jti
 ↓
Is jti revoked?
 ↓
Reject / Continue

This introduces server-side state, partially reducing one of the advantages of stateless JWT authentication.

Whether this is appropriate depends on the application's architecture and security requirements.


24. Understanding the kid Header

JWT headers sometimes contain:

{
  "alg": "RS256",
  "kid": "key-2026"
}

kid means:

Key ID

It helps the verifier identify which key should be used.

For example:

key-2025
key-2026
key-2027

This becomes especially useful during key rotation.

The server can use:

kid = key-2026

to select the corresponding trusted public key.

However, insecure handling of attacker-controlled kid values can create additional vulnerabilities.


25. kid Manipulation

Consider a dangerous implementation:

key = load("/keys/" + kid)

If the kid parameter is taken directly from the JWT without validation, unexpected values could influence key selection.

For example, malicious input containing path manipulation sequences may interact with unsafe file-handling logic.

The broader security principle is:

Never directly concatenate untrusted JWT header values into sensitive operations.

A kid value should not directly control:

Filesystem paths
SQL queries
Shell commands
Remote requests

A safer design is:

kid
 ↓
Allowlisted identifier
 ↓
Trusted key mapping

For example:

key-1 → PUBLIC_KEY_1
key-2 → PUBLIC_KEY_2

Unknown identifiers should be rejected.


26. JWK and JWKS

Modern JWT infrastructure often uses:

JWK
JWKS

JWK means:

JSON Web Key

JWKS means:

JSON Web Key Set

A public JWKS endpoint may look like:

/.well-known/jwks.json

Example content:

{
  "keys": [
    {
      "kty": "RSA",
      "kid": "key1",
      "use": "sig",
      "n": "...",
      "e": "AQAB"
    }
  ]
}

Publishing public verification keys is not inherently a vulnerability.

In asymmetric cryptography:

Public Key → verification
Private Key → signing

The private key is the critical secret.


27. The jku Header

JWT may also contain:

jku

which can reference a JWK Set URL.

Example:

{
  "alg": "RS256",
  "jku": "https://keys.example.com/jwks.json",
  "kid": "key1"
}

The critical security question is:

Does the application trust arbitrary jku URLs supplied by the token?

A secure application should normally restrict key sources to explicitly trusted locations.

Blindly retrieving attacker-controlled key URLs can introduce serious trust problems and, depending on the implementation and network environment, may also create server-side request risks.


28. The x5u Header

Similarly:

x5u

may identify a URL containing X.509 certificate material.

Example:

{
  "alg": "RS256",
  "x5u": "https://example.com/certs.pem"
}

The same principle applies:

A token should not be able to arbitrarily redefine the trust source used to verify itself.

Remote key and certificate sources should be tightly controlled.


29. Key Rotation

Cryptographic signing keys should have a managed lifecycle.

An organization might rotate keys periodically:

2025 → key-A
2026 → key-B
2027 → key-C

Tokens can identify their signing key using:

{
  "kid": "key-B"
}

During rotation, systems may temporarily need to support multiple trusted verification keys.

Key lifecycle management should consider:

Generation
Storage
Distribution
Activation
Rotation
Revocation
Retirement
Destruction

Key rotation is not simply about generating another key.

The entire lifecycle must be managed securely.


30. Private Key Leakage

With asymmetric JWT algorithms such as RS256, the private signing key is a critical asset.

If the private key is compromised, an attacker may potentially generate tokens that pass signature verification.

Private keys should never accidentally appear in:

Git repositories
Docker images
Application logs
Public storage buckets
Backups
CI/CD artifacts
Configuration repositories
Developer workstations
Support tickets

A leaked key may resemble:

-----BEGIN PRIVATE KEY-----
...
-----END PRIVATE KEY-----

Secret scanning, repository protection, access control, and key rotation procedures are therefore important parts of JWT security.


31. Sensitive Data Inside JWTs

Because JWT payloads can normally be decoded, designs such as:

{
  "username": "kutay",
  "password": "MyPassword123",
  "credit_card": "..."
}

are dangerous.

A useful design rule is:

Do not place information inside a signed JWT if disclosure of that information would itself create a security problem.

Often, minimal claims are preferable:

{
  "sub": "user-123",
  "role": "user"
}

Even then, privacy requirements should be considered.


32. Excessive Token Lifetime

Long-lived access tokens increase the useful lifetime of stolen credentials.

Consider a token with an extremely distant expiration timestamp.

If the token is stolen today and remains valid for months, the security impact of that credential leak becomes much larger.

A common architecture is:

Short-Lived Access Token
          +
Longer-Lived Refresh Token

However, there is no universally correct lifetime.

The appropriate value depends on:

Application sensitivity
User experience
Threat model
Revocation capabilities
Authentication strength
Business requirements

A financial application may reasonably use different session policies from a low-risk content platform.


33. Authentication Is Not Authorization

A valid JWT proves only what the application has configured it to prove.

It does not automatically mean the authenticated user has permission to access every requested resource.

Consider:

GET /api/users/500/orders
Authorization: Bearer <VALID_TOKEN>

Suppose the token belongs to:

user_id = 100

If the backend checks only:

Is JWT valid?

the application may allow one user to access another user's data.

The correct authorization flow should resemble:

JWT valid?
     ↓
Who is the user?
     ↓
Which resource is requested?
     ↓
Does this user have permission?
     ↓
Allow / Deny

JWT does not prevent vulnerabilities such as:

IDOR
BOLA
Broken Access Control
Privilege Escalation

These require proper server-side authorization controls.


34. Token Type Confusion

Modern authentication ecosystems may use several token types:

Access Token
Refresh Token
ID Token
Password Reset Token
Email Verification Token

These tokens serve different purposes.

For example, in OpenID Connect:

ID Token

communicates information about authentication and user identity.

An:

Access Token

is intended to authorize access to protected resources.

An API should not automatically accept every correctly signed token as an access token.

Validation should consider:

Who issued this token?
Who is it intended for?
What type of token is it?
What operations is it allowed to authorize?

35. Cross-Service JWT Reuse

Consider:

                 AUTH
                   |
        -----------------------
        |          |          |
     PROFILE     ORDERS    PAYMENTS

Suppose every service uses:

Same secret
Same issuer
No audience validation

A token intended for one service may potentially be accepted by another.

This weakens service boundaries.

Sensitive microservice environments should therefore consider:

Audience validation
Scopes
Service-specific authorization
Appropriate key architecture
Least privilege

36. Scope Validation

OAuth-based access tokens may contain scopes such as:

{
  "scope": "profile:read"
}

Suppose an endpoint performs:

DELETE /users/123

and requires:

users:delete

A backend that validates only the token's signature without validating its scope may allow unauthorized operations.

A robust authorization pipeline may resemble:

Valid Token
     ↓
Correct Issuer
     ↓
Correct Audience
     ↓
Required Scope
     ↓
Resource-Level Authorization
     ↓
Allow

Every layer solves a different security problem.


37. JWT Security Testing Checklist

During an authorized security assessment, the following checklist can help structure JWT analysis.

Token Structure

[ ] Decode Header
[ ] Decode Payload
[ ] Identify algorithm
[ ] Identify token type
[ ] Identify security-sensitive claims

Signature Validation

[ ] Is the signature actually verified?
[ ] Is "none" rejected?
[ ] Is an algorithm allowlist enforced?
[ ] Could algorithm confusion exist?

HMAC Secret Security

[ ] Is the secret sufficiently strong?
[ ] Are default secrets used?
[ ] Is the secret hard-coded?
[ ] Does the secret appear in repository history?
[ ] Is the same secret reused across environments?

Claims

[ ] Is exp validated?
[ ] Is nbf validated?
[ ] Is iss validated?
[ ] Is aud validated?
[ ] Is the token type validated?

Authorization

[ ] Are role checks enforced server-side?
[ ] Are scopes validated?
[ ] Is object-level authorization enforced?
[ ] Are administrative endpoints independently protected?
[ ] Are tenant boundaries enforced?

Token Storage

[ ] Is the token stored in LocalStorage?
[ ] Are cookies HttpOnly?
[ ] Are cookies Secure?
[ ] Is SameSite configured appropriately?
[ ] Could tokens leak through URLs?
[ ] Are tokens written to logs?

Token Lifecycle

[ ] Is the access-token lifetime reasonable?
[ ] Are refresh tokens rotated?
[ ] Is refresh-token reuse detected?
[ ] Does logout revoke server-side credentials?
[ ] Is there a revocation mechanism?

Key Management

[ ] Is kid securely handled?
[ ] Are jku sources restricted?
[ ] Are x5u sources restricted?
[ ] Are signing keys rotated?
[ ] Are private keys securely stored?
[ ] Are secrets isolated between environments?

38. JWT Security Testing Workflow

A structured JWT assessment can be visualized as:

JWT Identified
      |
      v
Decode Header & Payload
      |
      v
Identify Algorithm
      |
      +------ HS256
      |         |
      |         v
      |   Secret Security
      |
      +------ RS256 / ES256
                |
                v
            Key Handling
                |
                v
         kid / jku / x5u
                |
                v
          Claim Validation
                |
        -------------------
        |      |      |   |
       exp    iss    aud  nbf
                |
                v
        Authorization Tests
                |
                v
      Roles / Scopes / Objects
                |
                v
          Token Lifecycle
                |
                v
     Refresh / Logout / Revocation

This workflow highlights an important point:

JWT assessment should not stop after inspecting the signing algorithm.


39. What Does a Secure JWT Implementation Look Like?

A strong JWT architecture relies on several independent security layers:

Strong Signing Algorithm
          ↓
Secure Key Management
          ↓
Strict Algorithm Allowlist
          ↓
Signature Verification
          ↓
Issuer Validation
          ↓
Audience Validation
          ↓
Expiration Validation
          ↓
Token Type Validation
          ↓
Scope / Role Validation
          ↓
Object-Level Authorization
          ↓
Secure Token Storage
          ↓
Refresh Token Security
          ↓
Revocation Strategy

No individual layer should be considered sufficient on its own.


40. Example Secure Validation Logic

Conceptually:

function validateToken(token):

    header = parseHeader(token)

    if header.algorithm != EXPECTED_ALGORITHM:
        reject()

    key = getTrustedKey(header.kid)

    if key == null:
        reject()

    claims = verifySignature(token, key)

    if claims.expired:
        reject()

    if claims.notBefore > currentTime:
        reject()

    if claims.issuer != EXPECTED_ISSUER:
        reject()

    if EXPECTED_AUDIENCE not in claims.audience:
        reject()

    return claims

Authorization should then be handled separately:

claims = validateToken(token)

user = loadUser(claims.sub)

if !user.active:
    reject()

if !user.canAccess(resource):
    reject()

allow()

This distinction is extremely important:

Authentication != Authorization

Authentication answers:

Who are you?

Authorization answers:

Are you allowed to perform this action?

JWT does not eliminate the need for either process.


41. Defensive JWT Security Checklist

Production JWT implementations should consider the following controls:

✓ Use strong signing keys
✓ Explicitly allowlist signing algorithms
✓ Always verify signatures
✓ Validate expiration
✓ Validate nbf where applicable
✓ Validate the expected issuer
✓ Validate the expected audience
✓ Validate token purpose/type
✓ Keep access tokens reasonably short-lived
✓ Protect refresh tokens
✓ Consider refresh token rotation
✓ Implement an appropriate revocation strategy
✓ Never place authentication tokens in URLs
✓ Avoid sensitive information inside JWT payloads
✓ Perform authorization server-side
✓ Validate scopes and permissions
✓ Enforce object-level authorization
✓ Treat kid as untrusted input
✓ Restrict jku/x5u key sources
✓ Secure signing keys
✓ Rotate keys appropriately
✓ Never hard-code production secrets
✓ Avoid unnecessary token logging
✓ Use HTTPS
✓ Monitor authentication anomalies

42. JWT Does Not Automatically Make an Application Secure

A common misconception is:

We use JWT
=
Our authentication is secure

In reality:

JWT
+
Key Management
+
Claim Validation
+
Authorization
+
Secure Storage
+
Session Lifecycle
+
Application Security

all contribute to the security of the authentication system.

An application can use perfectly signed JWTs and still contain:

IDOR / BOLA
Broken Access Control
XSS
CSRF
SSRF
SQL Injection
Business Logic Vulnerabilities
Privilege Escalation

JWT only addresses part of the overall application-security problem.


43. The Most Important Pentester Mindset

When a penetration tester encounters a JWT, the question should not simply be:

Can I break the JWT?

A much more useful question is:

What security decisions does this application trust the JWT to make?

Suppose the token contains:

{
  "sub": "123",
  "role": "user",
  "tenant": "company-a",
  "scope": "read"
}

This creates several areas to investigate:

How is sub used?

How is role validated?

Is tenant isolation enforced server-side?

Are scopes actually checked?

What happens if the user is disabled?

What happens if the user's role changes?

Does the old token retain previous permissions?

Does logout invalidate the credential?

Can the same token be used against another API?

Are critical authorization decisions based on stale claims?

In many real-world applications, the most serious JWT-related vulnerabilities are not weaknesses in the cryptographic algorithm itself.

They originate from mistakes in the surrounding:

Trust Model
Authorization Logic
Key Management
Token Lifecycle
Service Architecture

44. Conclusion

JWT is a powerful and flexible standard, but secure implementation requires significantly more than simply generating and verifying a token.

Important areas include:

Algorithm Validation
Signature Verification
Strong Secrets
Key Management
Claim Validation
Expiration
Issuer Validation
Audience Validation
Authorization
Token Storage
Refresh Token Security
Revocation

JWT security should never be reduced to:

Is the token signed?

Instead, security teams should ask:

How is the token generated?
        ↓
How is it verified?
        ↓
Where is it stored?
        ↓
Which services accept it?
        ↓
Which security decisions depend on it?
        ↓
How is it refreshed?
        ↓
How is it revoked?

The fundamental principle of secure JWT design is:

Never trust a security-sensitive value from a token until the token, its claims, its intended purpose, and the resulting authorization decision have all been properly validated.