What exactly is 'JwtBearerDefaults.AuthenticationScheme' and 'JwtBearerDefaults' for? - c#

What exactly is the call JwtBearerDefaults.AuthenticationScheme and JwtBearerDefaults for?
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidIssuer = tokenOptions.Issuer,
ValidAudience = tokenOptions.Audience,
ValidateIssuerSigningKey = true,
IssuerSigningKey = SecurityKeyHelper.CreateSecurityKey(tokenOptions.SecurityKey)
};
});

JwtBearerDefaults.AuthenticationScheme is a constant with the value "Bearer". This is just text that is similar in intent to the authentication provider.
When you call AddAuthentication, you need to provide the default authentication provider. Providers are named. By default AddJwtBearer will create an authentication provider with the name, yep you guessed it, JwtBearerDefaults.AuthenticationScheme.
You could ignore the JwtBearerDefaults class altogether if you'd like, this is personal preference. Pass whatever string you want to AddAuthentication, then use the overload of AddJwtBearer that takes a string and an Action to customize the provider's name.
builder.Services.AddAuthentication("MyCoolAuthProvider")
.AddJwtBearer("MyCoolAuthProvider", options => {
//...
});
In the end, why do people use the JwtBearerDefaults class? Same reason you name your for loop variable i. It's what the documentation suggests, and everyone else does it. At least if it's a constant, you can't typo the value.

Related

Fail to validate issuer signing key for JWT

I have an end point which is protected by the [Authorize] attribute. I want to allow access to it, when a token is provided that is signed with the phrase "Super-Secret-Key" For now, I just generate the token with Jwt.io (you can see a screenshot at the end of the post). However, when I visit the end point, just get a 401 Unauthorized, even if I send the encoded token along as a bearer token (using Postman).
What am I missing?
Program.cs
var tokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
var builder = WebApplication.CreateBuilder(args);
{
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options => options.TokenValidationParameters = tokenValidationParameters);
builder.Services.AddAuthorization();
builder.Services.AddControllers();
}
(...)
the issue is that asp.net core tries to validate the token issuer by default that you did not set up on jwt.io. Set ValidateIssuer = false inside TokenValidationParameters:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateIssuer = false,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
});
If you want to specify an issuer to your JWT token you have to add the iss standard claim (Payload data) as specified inside official docs: https://jwt.io/introduction.
Here's an example:
Validation setup:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateAudience = false,
ValidateLifetime = false,
ValidateIssuerSigningKey = true,
ValidateIssuer = true, //Validate issuer
ValidIssuer = "MyIssuer", //Issuer to validate
//ValidIssuers = new List<string>() { "MyIssuer", "MySecondIssuer" }, //You can specify multiple issuers
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("Super-Secret-Key"))
};
});
There also are other "standard" (registered) claims that you can use like:
exp: timestamp that identifies the expiration time of the token.
nbf: timestamp that identifies the time before which the JWT
must not be accepted for processing.
aud: audience identifies recipients that the JWT is
intended for.
that you can validate with standard validation options or with custom-written validation logic.
See: https://datatracker.ietf.org/doc/html/rfc7519#section-4.1

Why am I randomly getting a 401 sometimes and not others using two different authentication policies?

I'm attempting to set up two different authorization policies called 'portal' and 'api' on our .NET Core 2.1 project hosted on a AWS Lambda environment. The first is the default policy that is used by the front-end site, and the latter is used by developers seeking to use our external API. I want each one to be signed with a different IssuerSigningKey so that users cannot use the bearer token they receive interchangeably with the two different services.
The problem I'm having is that the bearer token generated to access the 'api' policy works sometimes, but not others. I will get five 401s in a row, then followed by several 200s, then back to 401s. Seems very random. The errors I'm getting when the 401s show up are
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: Failed to validate the token.
Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerHandler: portal was not authenticated. Failure message: IDX10503: Signature validation failed. Keys tried: 'Microsoft.IdentityModel.Tokens.SymmetricSecurityKey , KeyId:
'.
Exceptions caught:
''.
token: '
{
"alg": "HS512",
"typ": "JWT"
}
.
{
"nameid": "88",
"unique_name": "my#email.com",
"role": "apiAccess",
"nbf": 1596835699,
"exp": 1596839299,
"iat": 1596835699
}
'.
Which implies to me that sometimes it's not attempting to use the 'api' policy to validate the bearer token, but instead it's defaulting to 'portal'.
Here is a sample in Startup.cs where I set this all up.
...
serviceDependencies
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("portal", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(portalAccessKey),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = true
};
})
.AddJwtBearer("api", options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(apiAccessKey),
ValidateIssuer = false,
ValidateAudience = false,
ValidateLifetime = false
};
});
serviceDependencies.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("portal")
.Build();
options.AddPolicy("MeteredAccess", new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("api")
.RequireClaim(ClaimTypes.Role, "apiAccess")
.Build());
});
...
And the MVC endpoint I am applying the policy to is
[Authorize(Policy = "MeteredAccess")]
[HttpPost("api/objects/save")]
public async Task<IActionResult> ApiCreateObject([FromBody] CoreEngineObjectFormData data)
{
if(!(await _authService.UpdateUserApiAuthentications(UserId)))
return StatusCode(429);
var result = await _coreEngineService.CreateCoreEngineObject(data);
return Ok(result);
}
I never have this problem with the default policy, requests made from the front-end work every time.
Also, a big thing to mention is that I don't have this problem at all when I run the project locally under IIS Express, it's only after I deploy the project AWS Lambda that this starts to happen. I suspect that something about the lambda's lifetime may have something to do with this.

ASP.NET Core API Authentication with JWT breaks built-in authentication for website

I'm working on an ASP.NET Core project that has a website as well as an API. I need the API to be secured by some means, so I implemented JWT authentication. This worked as expected, and the API is only accessible by calls made with a valid token.
The problem is, the built-in login functionality on the website no longer works.
This is the code I added to Startup.cs:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(o =>
{
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = Configuration["Jwt:Issuer"],
ValidAudience = Configuration["Jwt:Issuer"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(Configuration["Jwt:Key"]))
};
});
If I remove this code, the built-in login feature works as usual, but the API no longer works.
Is there a way I can make the JWT authentication only apply to the API controllers? Or is there a way I can make the built-in authentication use JWT?
I had the same problem and resolved it (using Asp.Net Core 3.0).
You can add more than one Authentication schema to your project with code like this.
// Read the token generation data...
var token = Configuration.GetSection("JwtTokenData").Get<JwtTokenData>();
services.AddAuthentication()
.AddCookie(options =>
{
options.LoginPath = $"/Identity/Account/Login";
options.AccessDeniedPath = $"/Identity/Account/AccessDenied";
})
.AddJwtBearer(options => {
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(token.Secret)),
ValidIssuer = token.Issuer,
ValidAudience = token.Audience,
ValidateIssuer = false,
ValidateAudience = false
};
});
Notice how I haven't specified the default authentication schema when I call AddAuthentication. This allows the built-in Authentication to be the default for your pages or controller where you use the [Authorize] attribute.
But when you need to use JWT Authentication then you use this option on the Authorize attribute for your Web API controller or methods
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]

Consuming token with AddJwtBearer( ) - fails to validate signature

I've tried for a couple of days to consume a jwt token in my .net core 2.1 web service. The token is rejected with a SecurityTokenInvalidSignatureException: IDX10503: Signature validation failed.-exception.
My setup is quite simple:
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(configureOptions => {
configureOptions.ClaimsIssuer = "http://localhost/";
configureOptions.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String("zeey+h0M8mvWSR9HYKlNns+....")),
ValidateIssuer = true,
ValidIssuer = "http://localhost/",
ValidateAudience = true,
ValidAudience = "efb4f12d020b434c9b531af96263b3fa",
};
});
Putting the token I receive into https://jwt.io/, the signature is found valid:
But my web service still refuses to acknowledge the token.
What am I doing wrong?
I finally found the answer to my problem:
My key was wrong. But even with the correct key, I still got the same error...
When constructing the SymmetricSecurityKey, I had to use Encoding.Unicode.GetBytes(...) instead of Encoding.ASCII or Encoding.UTF8.
I'm still having trouble with jwt.io. I guess this site is no longer a good place for validating tokens.

JwtToken AudienceValidator missing audience in IEnumerable<string> audiences

I have an asp net core api where I need a custom validation for the "audience" claim in a JWT token. This is possible via the AudienceValidator delegate (Microsoft.IdentityModel.Tokens) with signature:
bool CustumValidator(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
The audiences should, as I understand it, contain a list of audiences in the token. The validationParameters contains all the TokenValidationParameters you register at startup. However, for me this parameter is empty.
In the configure method of my startup class if have the following:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IApplicationLifetime applicationLifetime)
{
//...
// Setup JWT for the application.
var jwtAppSettingOptions = ConfigurationRoot.GetSection(nameof(JwtIssuerOptions));
var tokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)],
ValidateAudience = true,
//ValidAudience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)], -- I have tried with both this property and the ValidAudiences one below
ValidAudiences = new[] { jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)] },
ValidateIssuerSigningKey = true,
IssuerSigningKey = _jwtSigningKey,
RequireExpirationTime = true,
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero,
AudienceValidator = DoValidation // <-- my custom validation method, described below
};
//....
}
And then I have this little test method, that for now, does nothing but return true.
private bool DoValidation(IEnumerable<string> audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
{
var castedToken = securityToken as JwtSecurityToken;
// Do Nothing
return true;
}
As said, this token in the piece above has no value in its audience property, and the IEnumerable<string> audiences is empty as well. I do not understand why. If I let the default validator method be (by not setting a delegate), it is able to see the token's audience and Forbids access if the audience is incorrect. So why does my custom method not get the audience(s) passed in?
Here's how I make the token:
var jwt = new JwtSecurityToken(
_jwtIssuerOptions.Issuer,
String.IsNullOrWhiteSpace(_aud) ? _jwtIssuerOptions.Audience : _aud,
claims,
_jwtIssuerOptions.NotBefore,
_jwtIssuerOptions.Expiration,
_jwtIssuerOptions.SigningCredentials);
Thanks in advance.
I came across this question while trying to find the signature for the AudienceValidator delegate. (Thanks for that!)
The audiences enumerable is populated for me when I handle that delegate. One difference I see is that I do not specify the ValidAudiences parameter. You might try excluding that (assuming you haven't already solved this). Here is an example of my working code:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "MyIssuer",
IssuerSigningKey = new SymmetricSecurityKey(
Encoding.UTF8.GetBytes(Configuration["SecurityKey"])),
AudienceValidator = validateAudience
};
});

Categories