A Critical Flaw in JWT Authentication Configuration
A critical misconfiguration in the JWT authentication setup allowed the most fundamental security property of bearer tokens—their signature—to be completely bypassed. By explicitly disabling cryptographic signature validation, the code accepted forged tokens from any source, turning a multi-step authentication chain into a no-op.
This is the kind of vulnerability that makes security teams lose sleep: it's not a complex attack requiring multiple steps, it's not a race condition or a parsing edge case—it's a single configuration line that inverts the entire security model of the system.
Affected Versions
| Affected | not applicable (first-party code) |
| Fixed in | not applicable (first-party code) |
| Ecosystem | N/A |
| CVE / GHSA | not assigned |
| CWE | CWE-347 |
The Vulnerability Explained
The JWT authentication handler in the infrastructure layer was configured with an explicit security flaw:
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = false // ← CRITICAL: signature validation disabled
};
This configuration tells the authentication middleware: "Check that the token was issued by a trusted issuer, check the audience claim, and check that it hasn't expired—but do not check the signature."
Here's why that's catastrophic.
A JWT token has three parts: a header, a payload (claims), and a signature. The signature proves that the payload hasn't been tampered with and was created by someone who knows the server's secret key. When ValidateIssuerSigningKey is false, the handler skips the signature check entirely.
An attacker exploits this by:
-
Creating a forged payload with arbitrary claims:
json { "sub": "admin", "role": "admin", "iss": "valid-issuer", "aud": "valid-audience", "exp": 2000000000 } -
Signing it with any key (even one they control) or no key at all:
javascript const forgedToken = jwt.sign( { sub: "admin", role: "admin" }, "attacker-secret", // Not the server's key { algorithm: "HS256" } ); -
Sending it as a bearer token:
bash curl -H "Authorization: Bearer <forgedToken>" \ http://localhost:3000/api/protected -
Bypassing authentication because the handler validates the claims (issuer, audience, expiration) but never checks the signature.
The other validation checks—ValidateIssuer, ValidateAudience, ValidateLifetime—become security theater. An attacker simply includes valid issuer and audience strings and sets an expiration date in the future. All checks pass. The token is accepted as legitimate.
For a publicly accessible API endpoint, this means anyone can create a token claiming to be any user, including administrators, and gain full access to protected resources.
The Fix
The fix is surgical: change one boolean value:
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true // ← Fixed: signature validation enabled
};
By setting ValidateIssuerSigningKey = true, the authentication handler now requires that:
- The token's signature is cryptographically valid
- The signature was created with the server's signing key (stored securely and never shared)
- Any modification to the token's claims would invalidate the signature
This restores the actual security property of JWT tokens: a forged token will have an invalid signature and be rejected immediately, regardless of what claims it contains.
The regression test in the PR explicitly covers the attack scenarios:
- A token signed with an attacker-controlled secret is rejected
- A token using the "none" algorithm (a known bypass technique) is rejected
- A token with valid structure but wrong key is rejected
All three now fail with HTTP 401, as intended.
Key Takeaways
-
Never set
ValidateIssuerSigningKey = falsein production JWT configurations. The issuer, audience, and expiration checks are worthless without signature validation. Signature validation is the root of JWT security, not an optional layer. -
Signature validation is orthogonal to other JWT checks. Validating claims like
issandexpdoes not validate the signature. An attacker can forge tokens with valid claims and any signature. -
Bearer token security is binary. Either the token is cryptographically signed by the server and verified on every request, or any attacker can create valid tokens. There is no middle ground or "good enough" partial validation.
-
Configuration mistakes in authentication are the highest-impact vulnerabilities. A single boolean can disable an entire security mechanism. Code review, static analysis, and automated security scanning are essential for authentication infrastructure.
How Orbis AppSec Detected This
- Source: Bearer tokens arrive in the HTTP
Authorizationheader and reach the JWT authentication handler's parameteroptions.TokenValidationParameters - Sink: The
ValidateIssuerSigningKeyproperty in the token validation configuration, which controls whetherSecurityTokenHandler.ValidateToken()checks the signature - Missing control: No enforcement that
ValidateIssuerSigningKeymust be set totrue; the configuration was set tofalse, completely disabling signature validation - CWE: CWE-347 (Improper Validation of Cryptographic Signature)
- Fix: Changed
ValidateIssuerSigningKeyfromfalsetotrue, forcing signature validation on all bearer tokens
Orbis AppSec automatically detected this vulnerability and opened a pull request with the fix. Try Orbis AppSec on your repositories to find and fix issues like this automatically.
Conclusion
Authentication misconfigurations are some of the most dangerous vulnerabilities because they silently invert security guarantees. Setting ValidateIssuerSigningKey = false explicitly disables the cryptographic validation that makes JWT tokens trustworthy. This fix restores that validation, ensuring that only tokens signed by the server are accepted and forged tokens are rejected outright. For any API handling sensitive operations or user data, this change is essential.