1. The alg:none attack

The JWT specification includes an alg header that tells the server which algorithm to use for signature verification. If your JWT library accepts "alg": "none", an attacker can craft a token with any payload, set the algorithm to none, remove the signature entirely, and your server will treat it as valid.

This is not theoretical. Older versions of popular libraries like jsonwebtoken (Node.js), PyJWT, and php-jwt were vulnerable. The fix: always specify the allowed algorithms explicitly when verifying tokens. Never let the token itself dictate which algorithm to use.

// VULNERABLE — trusts the token's alg claim
jwt.verify(token, secret);

// SAFE — explicitly restricts allowed algorithms
jwt.verify(token, secret, { algorithms: ['HS256'] });

2. Weak signing secrets

If you use HMAC-based algorithms (HS256, HS384, HS512), the security of every token depends entirely on the strength of your secret. We've cracked JWT secrets during pentests using hashcat with a wordlist in under 10 minutes. Common weak secrets: secret, password, company names, your-256-bit-secret (the default from jwt.io).

Generate secrets with at least 256 bits of entropy: openssl rand -base64 64. Store them in a secret manager, not in source code or .env files committed to git. Rotate the secret on a defined schedule and whenever team members with access leave.

Pentest finding

JWT secret cracked from wordlist

During a security assessment of a Nigerian mobile money platform, we extracted a JWT from the mobile app's traffic, cracked the HMAC secret using hashcat in 8 minutes (the secret was the company name in lowercase), and forged admin-level tokens. Full account takeover of any user was possible.

3. No token expiration

JWTs without an exp (expiration) claim are valid forever. If a token leaks through logs, a compromised device, or a man-in-the-middle attack, the attacker has permanent access. This is especially dangerous for fintech where tokens grant access to financial operations.

Set short expiration times for access tokens — 15 minutes is a good baseline for fintech. Use refresh tokens (stored securely, with rotation) to issue new access tokens without forcing re-authentication. Always validate the exp claim server-side. Libraries handle this by default, but verify your configuration doesn't disable the check.

4. Storing tokens in localStorage

For web applications, storing JWTs in localStorage or sessionStorage exposes them to cross-site scripting (XSS) attacks. A single XSS vulnerability anywhere on your domain allows an attacker to execute localStorage.getItem('token') and exfiltrate every user's JWT.

For SPAs, the more secure approach is to store tokens in httpOnly, secure, sameSite cookies. The browser sends them automatically, and JavaScript cannot access them — eliminating the XSS exfiltration vector. Yes, this requires CSRF protection, but CSRF is a solved problem with sameSite=Lax or sameSite=Strict cookies and anti-CSRF tokens.

For mobile apps, use the platform's secure storage: Android Keystore or iOS Keychain. Never store tokens in SharedPreferences (Android) or UserDefaults (iOS) without encryption.

5. Not validating issuer and audience

The iss (issuer) and aud (audience) claims tell the server who created the token and who it was created for. If you don't validate these, a token from one service can be used to authenticate against a completely different service — especially dangerous in microservices architectures where multiple services share infrastructure.

// Set claims when creating the token
const token = jwt.sign(
  { userId: user.id },
  secret,
  {
    algorithm: 'HS256',
    expiresIn: '15m',
    issuer: 'api.yourfintech.com',
    audience: 'app.yourfintech.com'
  }
);

// Validate claims when verifying
jwt.verify(token, secret, {
  algorithms: ['HS256'],
  issuer: 'api.yourfintech.com',
  audience: 'app.yourfintech.com'
});

6. Missing token revocation

JWTs are stateless by design — the server doesn't store them. This means you can't "log out" a user by deleting a session. If a user's account is compromised, if they change their password, or if you need to force a logout, the existing JWT remains valid until it expires.

For fintech, you need a revocation mechanism. Common approaches:

Not sure if your JWT implementation has these vulnerabilities? We test authentication flows in every API security engagement.

Get an authentication security audit

7. Symmetric vs asymmetric keys

HMAC algorithms (HS256) use a single shared secret for both signing and verification. This means every service that needs to verify tokens must have the secret — and any service with the secret can forge tokens. In a monolithic API, this is fine. In a microservices architecture, it's a single-point-of-compromise risk.

RSA or ECDSA algorithms (RS256, ES256) use a private key for signing and a public key for verification. Only the auth service holds the private key. All other services verify with the public key — they can confirm a token is legitimate but cannot create new ones. For any fintech with multiple services, asymmetric signing is the correct choice.

There's also the algorithm confusion attack: if your server is configured for RS256 but accepts HS256, an attacker can use the RSA public key (which is public) as the HMAC secret and forge valid tokens. Prevent this by always specifying allowed algorithms explicitly during verification.

Implementation checklist

Key takeaway

JWTs are not secure by default

JWTs are a token format, not a security solution. Their security depends entirely on how you configure signing, expiration, storage, validation, and revocation. Get any one of these wrong in a fintech context, and you've handed attackers the keys to your users' financial data.

Related reading

Blog: Securing Express APIs · Securing Django APIs · Microservices security

Guides: OWASP for fintech · Security checklist · Pentest report explained

Services: Authentication security · API security · Penetration testing