HttpContext.User.Claims always empty despite JWT containing claims - asp.net

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.

Related

keycloack with dotnet simple API

Using this or https://nikiforovall.github.io/aspnetcore/dotnet/2022/08/24/dotnet-keycloak-auth.html tutorial I have setup test user and realm. I can call localhost:8080/realms/Test/protocol/openid-connect/token with client secret and user id and password from postman and it gives me access and refresh token. Now I need to call dotnet endpoint and make sure the user is who he is. But I can not find a way to establish this part as I'm always getting 401 unauthorized. Perhaps it is not setup or my authorization bearer string is not formed correctly.
How can I simply call to an endpoint, check authorization and return a response back?
Dotnet Code:
using System.Security.Claims;
using Api;
using Keycloak.AuthServices.Authentication;
using Keycloak.AuthServices.Authorization;
using Keycloak.AuthServices.Sdk.Admin;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
var configuration = builder.Configuration;
var host = builder.Host;
host.ConfigureLogger();
services
.AddEndpointsApiExplorer()
.AddSwagger();
var authenticationOptions = configuration
.GetSection(KeycloakAuthenticationOptions.Section)
.Get<KeycloakAuthenticationOptions>();
services.AddKeycloakAuthentication(authenticationOptions);
var authorizationOptions = configuration
.GetSection(KeycloakProtectionClientOptions.Section)
.Get<KeycloakProtectionClientOptions>();
services
.AddAuthorization(o => o.AddPolicy("IsAdmin", b =>
{
b.RequireResourceRoles("default-roles-test");
/*b.RequireRealmRoles("admin");
b.RequireResourceRoles("r-admin");
// TokenValidationParameters.RoleClaimType is overriden
// by KeycloakRolesClaimsTransformation
b.RequireRole("r-admin");*/
})
)
.AddKeycloakAuthorization(authorizationOptions);
var adminClientOptions = configuration
.GetSection(KeycloakAdminClientOptions.Section)
.Get<KeycloakAdminClientOptions>();
services.AddKeycloakAdminHttpClient(adminClientOptions);
var app = builder.Build();
app
.UseSwagger()
.UseSwaggerUI();
app.UseAuthentication();
app.UseAuthorization();
app.MapGet("/", (ClaimsPrincipal user) =>
{
// TokenValidationParameters.NameClaimType is overriden based on keycloak specific claim
app.Logger.LogInformation("{#User}", user.Identity.Name);
return "Hello world. "+ user.Identity.Name;
}).RequireAuthorization("IsAdmin");
app.Run();
appsettings.json keycloack config:
"Keycloak": {
"realm": "Test",
"auth-server-url": "http://localhost:8080/",
"ssl-required": "none",
"resource": "test-client",
"verify-token-audience": false,
"client-secret": "P4JgvFhjY0ftGSLDYmYn7diZhjoLnHon",
"confidential-port": 0
}
Request sending to this endpoint from postman (perhaps the issue is here with correct sending format):

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]

AddDownstreamWebApi fails after adding multiple auth schemes

Problem: My as an app calls to a downstream web api throw a null exception error after adding my own jwt bearer authentication.
I have a .net 5 web API, call it AppAPI, whose ConfigureServices has the following code:
var accessTokenKey = Convert.FromBase64String(Configuration.GetValue<string>("AccessCodeSecret"));
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer("AccessToken", o =>
{
o.RequireHttpsMetadata = false;
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(accessTokenKey),
ValidateIssuer = false,
ValidateAudience = false
};
})
.AddMicrosoftIdentityWebApi(Configuration, "AzureAd", "AzureAd")
.EnableTokenAcquisitionToCallDownstreamApi()
.AddDownstreamWebApi("CommonServicesApi", Configuration.GetSection("CommonServicesApi"))
.AddInMemoryTokenCaches();
//services.AddAuthorization();
services.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AccessToken")
.Build();
});
I have an /auth endpoint that accepts an access token from Azure AD, and generates a new access token with my own custom claims based on the database. The controller uses an authorize attribute to ensure it uses the correct mechanism:
[Authorize(AuthenticationSchemes = "AzureAd")]
The default policy from above ensures the rest of the endpoints use this access token for every other endpoint that doesnt specify a scheme.
I have a 2nd web API, called CommonServices, that is only accessible from other APIs, not clients directly. So AppAPI uses AddDownstreamwebapi to handle those calls. This worked previously to me adding my own app access tokens, meaning I only had one auth mechanism - AddMicrosoftIdentityWebApi. I started receiving my error when I added my own JwtBearer auth - "AccessToken".
The controller that has the error injects IDownstreamWebApi commonServicesApi. It uses the default auth scheme of "AccessToken". The code looks like this:
var response = await _commonServicesApi.CallWebApiForAppAsync("CommonServicesApi", "AzureAd",
options => { options.RelativePath = "Projects"});
var json = await response.Content.ReadAsStringAsync();
The 2nd parameter "AzureAd" was my attempt to have the commonservicesApi use the correct scheme. I am not even sure if that's the right scheme to use, or if .EnbleTokenAcquisitionToCallDownstreamApi adds a 3rd scheme that should be specified.
It is this call that I receive
System.NullReferenceException: 'Object reference not set to an instance of an object.'
at Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
This exception was originally thrown at this call stack:
Microsoft.Identity.Web.MergedOptions.PrepareAuthorityInstanceForMsal()
Microsoft.Identity.Web.TokenAcquisition.BuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetOrBuildConfidentialClientApplication(Microsoft.Identity.Web.MergedOptions)
Microsoft.Identity.Web.TokenAcquisition.GetAuthenticationResultForAppAsync(string, string, string, Microsoft.Identity.Web.TokenAcquisitionOptions)
Microsoft.Identity.Web.DownstreamWebApi.CallWebApiForAppAsync(string, string, System.Action<Microsoft.Identity.Web.DownstreamWebApiOptions>, System.Net.Http.StringContent)
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()
I can't seem to figure out what is null, or how to approach solving this problem.

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.

Multiple Authentication Middlewares ASP.NET Core

I am relatively new to the concept of middlewares. I am aware that a middleware calls the next middleware when it completes.
I am trying to authenticate a request using either Google or my Identity Server. The user can login on my mobile app with google or a local account. However, I can't figure out how to use both authentication middlewares. If I pass the id_token for google, it passes on the first middleware (UseJwtBearerAuthentication) but fails on the second one (UseIdentityServerAuthentication). How can I make it so that it doesn't throw error when it actually passes on at least 1 authentication middleware? For example, if it passes on the first middleware, the second middleware is ignored?
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
Authority = "https://accounts.google.com",
Audience = "secret.apps.googleusercontent.com",
TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidIssuer = "accounts.google.com"
},
RequireHttpsMetadata = false
});
app.UseIdentityServerAuthentication(new IdentityServerAuthenticationOptions
{
Authority = "http://localhost:1000/",
RequireHttpsMetadata = false,
ScopeName = "MyApp.Api"
});
Normally, when an authentication middleware is failed(i don't mean throwing exception), this doesn't affect another successful authentication middleware. Probably your second middleware throws an exception(not a validation failure). First check error message and try to resolve it. If you can't, use AuthenticationFailed event to handle error. In this case your code should be something like below:
app.UseJwtBearerAuthentication(new JwtBearerOptions()
{
// ...
Events = new JwtBearerEvents()
{
OnAuthenticationFailed = async (context) =>
{
if (context.Exception is your exception)
{
context.SkipToNextMiddleware();
}
}
}
});
However, for your scenerio i wouldn't choose your way. I would use only identity server endpoint. For signing with google you can configure identity server like below:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
AutomaticAuthenticate = false,
AutomaticChallenge = false
});
app.UseGoogleAuthentication(new GoogleOptions
{
AuthenticationScheme = "Google",
SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme,
ClientId = "",
ClientSecret = ""
});
app.UseIdentityServer();
Edit
It seems AuthenticationFailed event couldn't be used for IdentityServer4.AccessTokenValidation. I am not sure but if you will use identity server for only jwt token, you can use UseJwtBearerAuthentication for validation.

Resources