v0.14 is out: a mobile auth pack for Swift/iOS and Android, catching insecure token storage, cleartext traffic, and OAuth in embedded WebViews. Read more →
MEDIUM AI PREVALENCE: HIGH auth.csharp.jwt.read-without-validation

A JWT is decoded with a read-only API that performs NO validation (new JwtSecurityToken(tokenString), handler.ReadJwtToken(...), or new JsonWebToken(tokenString)).

Why AI tools produce this: AI coding tools generate this anti-pattern by default, it appears in a large share of AI-written auth code.

Why this matters

These constructors/methods only parse the token; they do not check the signature, issuer, audience, or expiry. Trusting the claims that come back lets an attacker forge any identity or role by hand-crafting an unsigned token (CWE-345). This is a common AI-generated shortcut for "reading the user id from the token" that silently skips verification.

Validate the token before trusting its claims: call handler.ValidateToken(token, tokenValidationParameters, out var validated) (or await handler.ValidateTokenAsync(...)) and read claims from the validated result. Only parse a token unvalidated when you are inspecting a token you just issued, never one received from a client.

VULNERABLE
vulnerable.cs
using System.IdentityModel.Tokens.Jwt;
using Microsoft.IdentityModel.JsonWebTokens;

public class TokenReader
{
    public string GetUserId(string incomingToken)
    {
        // ruleid: auth.csharp.jwt.read-without-validation
        var parsed = new JwtSecurityToken(incomingToken);
        return parsed.Subject;
    }

    public string GetIssuer(string incomingToken)
    {
        var handler = new JwtSecurityTokenHandler();
        // ruleid: auth.csharp.jwt.read-without-validation
        var jwt = handler.ReadJwtToken(incomingToken);
        return jwt.Issuer;
    }

    public string GetAudience(string incomingToken)
    {
        // ruleid: auth.csharp.jwt.read-without-validation
        var jwt = new JsonWebToken(incomingToken);
        return jwt.Audiences.ToString();
    }
}
SAFE
safe.cs
using System;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;

public class TokenReader
{
    // Claims are read from a VALIDATED token, not a raw parse.
    public string GetUserId(string incomingToken, TokenValidationParameters parameters)
    {
        var handler = new JwtSecurityTokenHandler();
        ClaimsPrincipal principal = handler.ValidateToken(incomingToken, parameters, out SecurityToken validated);
        return principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
    }

    // Multi-argument constructor CREATES a token we are about to sign/issue — this
    // is not a decode-and-trust of an incoming token, so it must not fire.
    public JwtSecurityToken Issue(string issuer, string audience, SigningCredentials creds)
    {
        return new JwtSecurityToken(issuer, audience, null, DateTime.UtcNow, DateTime.UtcNow.AddMinutes(5), creds);
    }
}

Suppressing this rule

If a finding is a genuine false positive, scope the suppression to the exact line and leave a reason, never disable the rule project-wide. Disable directives are line-scoped by design.

// oauthlint-disable-next-line auth.csharp.jwt.read-without-validation -- <reason>

References

https://learn.microsoft.com/dotnet/api/system.identitymodel.tokens.jwt.jwtsecuritytokenhandler.validatetoken ↗https://cwe.mitre.org/data/definitions/345.html ↗