Why are the SigningKeys in TokenValidationParameters always null - .net-core

I'm trying to user AddJwtBearer to Authorize my API's.
I'm using this in my Startup.cs
services.AddControllers();
AddJwtBearer(options =>
{
options.IncludeErrorDetails = true;
options.MetadataAddress = Configuration["Issuer:uri"] + Configuration["Issuer:well-known"];
options.Authority = Configuration["Issuer:uri"];
options.TokenValidationParameters = new TokenValidationParameters
{
ValidIssuer = Configuration["Issuer:uri"],
ValidateIssuer = true,
ValidateIssuerSigningKey = true,
ValidateAudience = false,
};
options.Events = new JwtBearerEvents
{
OnAuthenticationFailed = context =>
{
if (context.Exception.GetType() == typeof(SecurityTokenExpiredException))
{
context.Response.Headers.Add("Token-Expired", "true");
}
return Task.CompletedTask;
}
};
});
But I'm always getting the error : IDX10501: Signature validation failed. Unable to match key: \nkid: '[PII is hidden. For more details, see https://aka.ms/IdentityModel/PII.]' because the IssuerSigningKeys are always null inside the TokenValidationParameters. Is there a way to load the issuer signing keys from the metadata url provided without writing specific code for that?
thanks

Thanks for the quick reply, but I figured it out. I had 2 issues, 1) I has specifying the .well-known/openid-configuration/jwks endpoint rather then just .well-known/openid-configuration, this alone instantiated my ConfigurationManager correctly; 2) I had to add app.UseAuthentication(); before app.UseAuthorization(); and now my code is correctly validating the JWT Bearer token signature against the correct Identity Server.

Related

HttpContext.User.Claims always empty despite JWT containing claims

I'm trying to create an asp-mvc api that reads the jwt claims on an incoming request, and then mirrors them back to the user - my use case is that I'm trying to investigate why a different endpoint is failing, I get a token from an external service, so I want minimal/no auth on it, I just want to inspect the claims.
I have the following Controller and endpoint:
[ApiController]
[Route("api/[controller]")]
public class TestController : ControllerBase
{
[HttpGet]
[Route("testclaims")]
public async Task<ActionResult<string?>> TestClaims()
{
List<string> result = new List<string>();
foreach (var claim in HttpContext.User.Claims)
result.Add($"{claim.Type}: {claim.Value}");
return "{ " + String.Join(", ", result.ToArray()) + " }";
}
}
My app is setup like this:
...
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme,
options => {
options.TokenValidationParameters = new TokenValidationParameters
{
RequireExpirationTime = false,
RequireSignedTokens = false,
RequireAudience = false,
SaveSigninToken = false,
TryAllIssuerSigningKeys = false,
ValidateActor = false,
ValidateAudience = false
};
builder.Configuration.Bind("JwtSettings", options);
});
var app = builder.Build();
app.UseHttpsRedirection();
app.UseAuthorization();
app.MapControllers();
app.Run();
I'm calling the endpoint from postman I make a request with an auth token containing a know set of claims, HttpContext.User.Claims is always empty, but I can see HttpContext.Request.Headers contains the expected token, and if I decode that externally, it contains the claims.
I've tried adding an Authorize decorator to the endpoint, but when I do I get a 401 returned with no log or debug info.
I'm assuming there's just something I need to enable to have asp populate the claims?
You need to use [Authorize].
Besides, you miss the app.UseAuthentication(); before your app.UseAuthorization();.
app.UseAuthentication();
app.UseAuthorization();
result:
Have a try, hope it can help you.

asp.net core OAuth access_token verification fails with "IDX10609: Decryption failed. No Keys tried: token: 'System.String'.:"

I have a single page application in Blazor WASM that connects to OAuth0 and receives JWT token when a user get authenticated via OAuth.
My OAuth application uses symmetric signing with HS256.
I am trying to authenticate the returned tokens using the "Client Secret" from my OAuth app but whereas the id_token gets verified, the access_token always fails with the error:
"IDX10609: Decryption failed. No Keys tried: token: 'System.String'.:"
To perform the verification I use the method found here.
My code looks like that:
var mySecret = "####";
var mySecurityKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(mySecret));
var tokenHandler = new JwtSecurityTokenHandler();
try
{
IConfigurationManager<OpenIdConnectConfiguration> configurationManager =
new ConfigurationManager<OpenIdConnectConfiguration>(
$"{my_oauth_app_domain}.well-known/openid-configuration",
new OpenIdConnectConfigurationRetriever());
OpenIdConnectConfiguration openIdConfig = await configurationManager.GetConfigurationAsync(CancellationToken.None);
var keys = openIdConfig.SigningKeys;
tokenHandler.ValidateToken(token, new TokenValidationParameters
{
ValidateAudience = false,
ValidateIssuer = false,
ValidateActor = false,
ValidateLifetime = false,
ValidateTokenReplay = false,
ValidateIssuerSigningKey = true,
IssuerSigningKey = mySecurityKey,
IssuerSigningKeys = openIdConfig.SigningKeys,
}, out SecurityToken validatedToken);
}
catch(Exception ex)
{
Console.WriteLine($"{ex.Message}: {ex.StackTrace}");
return false;
}
return true;
What I find strange is that the access_token appears to be malformed. I know that it doesn't have to follow the JWT specs but puting it in jwt.io causes an error and fields like "kid" which I have seen in other tokens on the web, are missing.
Here is the token:
eyJhbGciOiJkaXIiLCJlbmMiOiJBMjU2R0NNIiwiaXNzIjoiaHR0cHM6Ly9nYW50b25vcG91bG9zLmV1LmF1dGgwLmNvbS8ifQ..MQnqWWfe3mBCVeKQ.y8e77jf3VwJNRSoDWjB3v05WrT9IJPL_kdqhxlQFnfMOAyqQJOD1ttl1muYlCJJVwAskaAeBr4FgkcwjiL1s4eS9gcWK7yq-2PPbLkDzXtBjA4kgMGdGyMURl-F2jYBNwxbCuXyBKcxJVwzE4-aluYCAOZ8QaXzqKgmQJdpxIdBluVux7nK49uhvEJ5Pv7ueh7eGcm9AAmHm__TYKPPcpPutHNiuD6J8xoptHFLPjKakECE6ZXgD-xLNp4BHwe_DmW6UDPuZ_OD9G8D-hwayz8l--zZdICEnFywUzSWXFiVPUvn4DszDhzWbJsBNf3dnl2cnKel3EYsB.NvsUTcP9v_iicpQ5AkaC4w
Am I doing something wrong?
I found the problem here. In my request to OAuth0 I did not add the "audience" parameter. This led to an opaque token being issued.

How to make jwt bearer token not required in .NET Core 6?

I have a configuration of JWT Bearer authentication, but sometimes instead of using JWT token, I want to use an API KEY in the request header and check this key in a middleware.
But in that case, when I don't put the bearer token in the header, I always respond with an Unauthorized response code.
How can I disable the bearer token check?
My configuration:
services.AddAuthentication(x =>
{
x.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
x.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
// options.RequireHttpsMetadata = false;
// options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
ValidateIssuer = true,
ValidateAudience = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(jwtSettings.Secret)),
ValidIssuer = jwtSettings.Issuer,
ValidAudiences = jwtSettings.Audiences,
ClockSkew = TimeSpan.Zero // remove delay of token when expire
};
});
Rather than checking in a middleware a more idiomatic way you can achieve this by using multipe AuthenticationSchemes. See the MSDN link for more details but at a very high level you can assign add multiple authentication schemes, each with a different scheme. You then refer to this scheme name when using the autorize attribute (e.g. [Authorize(AuthenticationSchemes = "Api-Key-Scheme")]).
services
.AddAuthentication()
.AddJwtBearer(options => { .. })
.AddApiKey(options => { .. }); // custom code
The .AddApiKey() method above will require a custom AuthenticationHandler<T> implementation, an example of how to do that can be found here - https://josef.codes/asp-net-core-protect-your-api-with-api-keys/
You can use the [AllowAnonymous] attribute on your method to disable the authentication check.
Then, create an ActionFilterAttribute and apply it to the same method:
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
namespace YourNameSpace
{
public class RequireYourKeyHeader : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
if(!filterContext.HttpContext.Request.Headers.TryGetValue("YourKey", out string headerValue))
{
filterContext.Result = new BadRequestObjectResult("missing header value");
}
// TODO: check if value passed in the header is actually valid
}
}
}
Apply with [RequireYourKeyHeader]

Authentication with MVC Client 4.7.1 and IdentityServer4

I am trying to integrate user authentication between an MVC 4.7.1 client and an ASP.NET Core 3.1 IdentityServer4 & ASP.NET Identity service.
I have been following this tutorial for cookie issued authentication: Refreshing your Legacy ASP.NET IdentityServer Client Applications (with PKCE)
So far, the MVC client is able to redirect to the Login page. Upon logging in, I have a null error in the following function:
private string RetrieveCodeVerifier(AuthorizationCodeReceivedNotification n)
{
string key = GetCodeVerifierKey(n.ProtocolMessage.State);
string codeVerifierCookie = n.Options.CookieManager.GetRequestCookie(n.OwinContext, key);
if (codeVerifierCookie != null)
{
var cookieOptions = new CookieOptions
{
SameSite = SameSiteMode.None,
HttpOnly = true,
Secure = n.Request.IsSecure
};
n.Options.CookieManager.DeleteCookie(n.OwinContext, key, cookieOptions);
}
string codeVerifier;
var cookieProperties = n.Options.StateDataFormat.Unprotect(Encoding.UTF8.GetString(Convert.FromBase64String(codeVerifierCookie)));
cookieProperties.Dictionary.TryGetValue("cv", out codeVerifier);
return codeVerifier;
}
Apparently the codeVerifierCookie is null.
The rest of the configuration is as follows.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = "cookie"
});
app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
{
ClientId = "mvc.owin",
Authority = "https://localhost:44355",
RedirectUri = "http://localhost:5001/Auth/",
Scope = "openid profile scope1",
SignInAsAuthenticationType = "cookie",
RequireHttpsMetadata = false,
UseTokenLifetime = false,
RedeemCode = true,
SaveTokens = true,
ClientSecret = "secret",
ResponseType = "code",
ResponseMode = "query",
Notifications = new OpenIdConnectAuthenticationNotifications
{
RedirectToIdentityProvider = n =>
{
if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
{
// set PKCE parameters
var codeVerifier = CryptoRandom.CreateUniqueId(32);
string codeChallenge;
using (var sha256 = SHA256.Create())
{
var challengeBytes = sha256.ComputeHash(Encoding.UTF8.GetBytes(codeVerifier));
codeChallenge = Base64Url.Encode(challengeBytes);
}
n.ProtocolMessage.SetParameter("code_challenge", codeChallenge);
n.ProtocolMessage.SetParameter("code_challenge_method", "S256");
// remember code_verifier (adapted from OWIN nonce cookie)
RememberCodeVerifier(n, codeVerifier);
}
return Task.CompletedTask;
},
AuthorizationCodeReceived = n =>
{
// get code_verifier
var codeVerifier = RetrieveCodeVerifier(n);
// attach code_verifier
n.TokenEndpointRequest.SetParameter("code_verifier", codeVerifier);
return Task.CompletedTask;
}
}
});
And on IdentityServer4 ConfigureServices:
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
var builder = services.AddIdentityServer(options =>
{
options.Events.RaiseErrorEvents = true;
options.Events.RaiseInformationEvents = true;
options.Events.RaiseFailureEvents = true;
options.Events.RaiseSuccessEvents = true;
// see https://identityserver4.readthedocs.io/en/latest/topics/resources.html
options.EmitStaticAudienceClaim = true;
})
.AddInMemoryIdentityResources(Config.IdentityResources)
.AddInMemoryApiScopes(Config.ApiScopes)
.AddInMemoryClients(Config.Clients)
.AddAspNetIdentity<ApplicationUser>();
Finally, Client.cs configuration on IdentityServer4 side:
new Client
{
ClientId = "mvc.owin",
ClientName = "MVC Client",
AllowedGrantTypes = GrantTypes.Code,
ClientSecrets = {new Secret("secret".Sha256())},
RedirectUris = {"http://localhost:5001/Auth/"},
AllowedScopes = {"openid", "profile", "scope1"},
AllowPlainTextPkce = false,
RequirePkce = true,
RequireConsent = false,
// Token lifetimes
AuthorizationCodeLifetime = 60,
AccessTokenLifetime = 60,
IdentityTokenLifetime = 60
}
AccountController and the rest is pretty much the basic IdentityServer4.Template, specifically is4aspid.
Anyone tried the same and knows what fails?
Is there a way to do it with JWT instead of Cookies? And, what are the drawbacks?
Edit: Apparently, this configuration works with Firefox, and I am suspecting this is a problem with Chrome's Same-Site cookie policy, hence the null in GetRequestCookie. The thing is, IdentityServer4 is running on HTTPS (otherwise, there are others) while the MVC client app is running on HTTP (note: both on localhost). I have tried using SameSite policy None, Lax, Strict and vise-versa with no success. I am not sure what else to try.
Best,
mkanakis.
Cookies that assert SameSite=None must also be marked as Secure, this means you need to use https
Read more here

The AuthorizationPolicy named: 'Bearer' was not found

Trying to add Jwt authentification to my DotNetCore 2.1 Server and Angular 6 App.
I've seen so many articles on the topic and no one seem's to do it the same way and nothing seem's to work for me either... i dont know what's wrong...
i'm getting : 'The AuthorizationPolicy named: 'Bearer' was not found.' when i start my server...
Services
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = "http://localhost:54523",
ValidAudience = "http://localhost:4300",
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes("tokensecret))
};
});
services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder =>
{
builder
.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials()
.Build();
});
});
services.AddMvc();
Configurations
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseMvc();
Controllers
[Authorize()]
[Route("api/[controller]")]
public class ProjectController : Controller
If i use the Controller [Authorize], when the user is not authentificated it return to /Account/Login?ReturnUrl=...
but it's JWT it should return 401, 403 only...
if i try with [Authorize(JwtBearerDefaults.AuthenticationScheme)] i'm getting 'The AuthorizationPolicy named: 'Bearer' was not found.'
but why...
EDIT
I didn't know that line was changeing the behaviour of authentification but I also use this line
serviceCollection.AddIdentity<User, Role>();
What's wrong ?
We cannot use Identity with JWT ?
how to configure it for JWT ?
Ok, I've found the way to get it working... finally!
You need to use AddIdentityCore instead of AddIdentity.
Then you need to configure it yourself and add the missings services that are not registered in AddIdentityCore.
link to AddIdentityCore method : https://github.com/aspnet/Identity/blob/9b385180a9abcb264507efc23279f083bfc50520/src/Core/IdentityServiceCollectionExtensions.cs
Identity Registration Code
var builder = serviceCollection.AddIdentityCore<User>(opt =>
{
opt.Password.RequireDigit = true;
opt.Password.RequiredLength = 8;
opt.Password.RequireNonAlphanumeric = true;
opt.Password.RequireUppercase = true;
opt.Password.RequireLowercase = true;
});
builder = new IdentityBuilder(builder.UserType, typeof(Role), builder.Services);
builder.AddEntityFrameworkStores<AmiliaContext>();
builder.AddDefaultTokenProviders();
builder.AddRoleValidator<RoleValidator<Role>>();
builder.AddRoleManager<RoleManager<Role>>();
builder.AddSignInManager<SignInManager<User>>();
serviceCollection.AddDependencies(Assembly.GetExecutingAssembly());
Additionnal Notes
User must inherit IdentityUser
Role must inherit IdentityRole
You must not use SignInAsync from the SignInManager, instead you need to use CheckPasswordSignInAsync.
Why ?
Because SignInAsync is using the cookie internaly so we cannot use this method in JWT.

Resources