CodeOath
← All posts
Auth & Security60 min total · 19 parts

OAuth 2.0 and JWT Explained: How "Login with Google" Actually Works

Contents — Part 11 of 19: Validating a JWT Correctly
Part 11 of 19 · ~2 min

Validating a JWT Correctly

Receiving a JWT and successfully base64-decoding its payload is not the same as validating it — and skipping real validation is one of the most common, serious mistakes in systems built around tokens. A correct verification step checks all of the following, not just "does the signature match something":

1. Signature — recompute it using the EXPECTED algorithm and key/secret (never the
   algorithm the token itself claims — see the alg-confusion attack above), and
   reject the token outright if it doesn't match.
2. exp — reject if the current time is at or past this token's expiration.
3. nbf — reject if the current time is before this token is allowed to be used.
4. iss — reject if this token wasn't issued by the authorization server you actually trust.
5. aud — reject if this token wasn't intended for your application/API specifically
   (a token legitimately issued for a DIFFERENT audience should never be accepted
   just because it happens to be validly signed by an issuer you trust).
// A realistic ASP.NET Core JWT bearer validation configuration — note that
// every check above is an explicit, named parameter, not implied behavior.
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = "https://accounts.google.com",
            ValidateAudience = true,
            ValidAudience = "your-client-id",
            ValidateLifetime = true,          // enforces exp and nbf
            ValidateIssuerSigningKey = true,
            IssuerSigningKey = signingKey,     // the EXPECTED key — never derived from the token
            ValidAlgorithms = new[] { "RS256" } // pin the algorithm explicitly
        };
    });

Skipping aud validation is a specific, realistic bug: a company running several internal APIs behind one shared identity provider can end up with a token legitimately issued for "the reporting service" being accepted by "the billing service" too, simply because both trust the same issuer and neither checks who the token was actually meant for.