In previous articles, we have spoken in length about what jwt is and how token based authentication helps securing APIs from unwanted access. We have also looked at differences between authentication and authorization, and how authentication or authorization of requests can be achieved in an aspnetcore API by means of middleware components. We also looked at created our own authentication and authorization schemes for validating requests and how they can be attached to the request pipeline, forcing aspnetcore to invoke our own authentication scheme or authorization policy.
Let’s look at a hypothetical scenario, wherein we would want the authentication middleware to completely be bypassed, but yet the user context for the request be setup for us to use at a later point in the request pipeline. sounds ridiculous right? But there can be such ridiculously unique but interesting scenarios out there. So how can we bypass the token validation for an incoming request on a particular condition (say ran out of testing tokens which are still of life?) but still would want to parse the token into setting up claims and have these claims be available in the succeeding middleware?
How Authentication Middleware works:
In general scenarios of a fully functioning authentication middleware which validates an incoming JWT token for prescribed criteria and decide whether the request can be allowed to proceed forward or be sent back to the user with a 401 UnAuthorized status code, the execution happens in the following steps:
- The Authentication Middleware checks for any Authorization header in the request which matches the configured scheme name (Bearer for example)
- If present, the middleware picks up the jwt token and try parses it
- Once parsed, the middleware extracts all the necessary information from the token headers such as issuer, audience, lifetime and others.
- Now the middleware compares these attributes of the incoming token against the expected attributes from a given configuration and boolean flags
- If all the attributes of the incoming token satisfy the expected attributes, it is flagged as "valid" and the request "authenticated"
- Once "authenticated", the middleware sets up a User Identity with claims obtained from the "payload" section of the token and sets the User.Identity.IsAuthenticated to true.
- If at any of the above steps fail, the middleware writes a 401 unauthorized to the response and reverts immediately
The Validation Config Flags:
To decide on what factors need to be taken into consideration for a token to be deemed "valid" or "invalid", we pass in an object of type TokenValidationParameters, which contain the necessary configurations for validating token. This TokenValidationParameter is configured in an Authentication middleware as below:
services.AddAuthentication(options =>
{
options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options => {
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = jwtTokenConfig.Issuer,
ValidAudience = jwtTokenConfig.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = JwtSecurityKey.Create(jwtTokenConfig.SecurityKey)
};
});
The flags ValidateIssuer, ValidateAudience, ValidateLifetime and ValidateIssuerSigningKey specify how the validation params are to be taken into consideration while validating a token.
To bypass the token validation, we can modify this TokenValidationParameters() object with all the flags disabled, and a redundant SecurityKey as below:
jwtOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = false,
ValidateAudience = false,
ValidateIssuerSigningKey = false,
SignatureValidator = delegate (string token,
TokenValidationParameters parameters)
{
var jwt = new JwtSecurityToken(token);
return jwt;
},
ValidateLifetime = false,
ClockSkew = TimeSpan.Zero,
RequireSignedTokens = false
};
in this case, we just create a SecurityToken() from the input token and return it as it is, without any validation. And observe that all the flags are disabled. When we put this in place of the TokenValidationParameters() we’re actually using in the middleware and pass a jwt token with no valid lifetime or configuration (but still should be a valid jwt token string though) we would still get into the resource, which in this case can be a succeeding middleware or an API which would require to access claims from a valid User Identity, we can find that the claims do exist and the user is authenticated.
Interesting isn’t it?