Location of UseRequestLocalization() call in Startup - c#

I have a custom implementation of IRequestCultureProvider. This provider checks if the user is not logged in, then it sets the current culture, otherwise it does not set the culture. Other culture providers would set the current culture, if user is logged in.
In Configure method I use UseRequestLocalization() after authentication middleware so that HttpContext.User property is populated. However, the property httpContext.User.Identity.IsAuthenticated is never true, and it always sets the culture.
The logs show that Culture Provider sets the culture prior to bearer token authentication middleware call, which should populate the HttpContext.User property.
I do not understand why.
public void Configure(IApplicationBuilder app, IHostingEnvironment env, IServiceProvider serviceProvider)
{
var localizationOptions = app.ApplicationServices.GetService<IOptions<RequestLocalizationOptions>>();
var defaultRequestCultureProvider = app.ApplicationServices.GetService<DefaultRequestCultureProvider>();
localizationOptions.RequestCultureProviders.Insert(1, defaultRequestCultureProvider);
app.Map("/api", webApiApp => ConfigureWebApiBranch(webApiApp,localizationOptions.Value));
// Removed for brevity
}
private void ConfigureWebApiBranch(IApplicationBuilder webApi, RequestLocalizationOptions localizationOptions)
{
webApi.UseCors(Constants.CorsPolicyName);
webApi.UseAuthentication();
webApi.UseRequestLocalization(localizationOptions);
webApi.UseMvc();
}
I am using IdentityServer4 bearer token authentication middleware.

There was no cookie attached with the request, so authentication was not successful.

Related

.NET Core does not show user claims in middleware when authenticating with Azure AD

I have a multi-tenant .NET Core web app where the current user's tenant is resolved via middleware. In particular, tenants are resolved with a library called SaasKit.Multitenancy.
To use this library, you put this line in ConfigureServices():
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// (omitted for brevity)
// The 'Tenant' type is what you resolve to, using 'ApplicationTenantResolver'
services.AddMultitenancy<Tenant, ApplicationTenantResolver>();
// ...
}
And you put this line in Configure() to add it to the middleware pipeline:
public void Configure(IApplicationBuilder app)
{
// ...
app.UseAuthentication();
app.UseMultitenancy<Tenant>(); //this line
app.UseMvc(ConfigureRoutes);
// ...
}
This causes the following method in the middleware to be executed, which resolves the current user's tenant:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
//whatever you need to do to figure out the tenant goes here.
}
This allows the result of this method (whichever tenant is resolved) to be injected into any class you want, like so:
private readonly Tenant _tenant;
public HomeController(Tenant tenant)
{
_tenant = tenant;
}
Up until now, we have been authenticating users with the .NET Identity platform, storing user data in our app's database. However, a new tenant of ours wants to be able to authenticate their users via SSO.
I have figured out most of the SSO stuff--I am using Azure AD to sign in users, and my organization's Azure AD tenant will be able to federate with their Identity Provider. In short, this code in ConfigureServices adds the Identity and AzureAD authentication:
public IServiceProvider ConfigureServices(IServiceCollection services)
{
// rest of the code is omitted for brevity
services.AddIdentity<ApplicationUser, ApplicationRole>(config =>
{
config.User.RequireUniqueEmail = true;
config.Password.RequiredLength = 12;
}).AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => _configuration.Bind("AzureAd", options)).AddCookie();
// policy gets user past [Authorize] if they are signed in with Identity OR Azure AD
services.AddMvc(options =>
{
var policy = new AuthorizationPolicyBuilder(
AzureADDefaults.AuthenticationScheme,
IdentityConstants.ApplicationScheme
).RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
}
When using Identity, I have been able to resolve the users's tenant with the UserManager, like so:
public async Task<TenantContext<Tenant>> ResolveAsync(HttpContext context)
{
TenantContext<Tenant> tenantContext = new TenantContext<Tenant>(new ApplicationTenant());
if (context.User.Identity.IsAuthenticated)
{
var user = await _userManager.Users
.Include(x => x.Tenant)
.FirstOrDefaultAsync(x => x.UserName == email);
if (user?.Tenant != null)
{
tenantContext = new TenantContext<Tenant>(user.Tenant);
_logger.LogDebug("The current tenant is " + user.Tenant.Name);
return await Task.FromResult(tenantContext);
}
}
return await Task.FromResult(tenantContext);
}
My plan was to modify this code so grabbed the current User's claims, which can be used to infer which tenant the user belongs to. However, when authenticating a user via Azure AD, HttpContext.User is always empty in the middleware, despite the user being signed in. It's not null, but HttpContext.User.Identity.IsAuthenticated is always false and HttpContext.User.Claims is empty. I only see the value of HttpContext.User populated once routing is complete and the code has reached a Controller.
I have tried reorganizing the middleware in pretty much every feasible way to no avail. What's confusing to me is that HttpContext.User is populated in the tenant resolver when the user is authenticated with Identity. With this in mind, I'm not sure how I can access the user's claims in the middleware when authenticating via Azure AD.
The best solution I can think of is to modify every instance the current tenant is injected into the code with a call to a method that resolves the tenant via claims. If the tenant is null in an area restricted with the [Authorize] attribute, it would imply the user is signed in via Azure AD, which would allow me to look at their claims. However, it really bothers me that I can't access the user's claims in the middleware, as I'm not sure what's really going on here.
Since you are trying to access HttpContext from a custom component you are going to want to add HttpContextAccessor to your service collection as follows:
services.AddHttpContextAccessor();
You can now resolve HttpContext as needed using dependency injection. This may or may not be helpful depending on how much control you have of the middleware that you are using.
For what it's worth, I've had little issue authenticating against AAD just using MSAL without additional third-party middleware. Good luck!
I suspect you might be running on .NET Core 2.0 and running into this issue.

ASP.NET core 2.2: what is the expected behaviour of ChallengeResult when there are multiple authentication schemes configured?

We are trying to understand what is the expected handling of a ChallengeResult when there are multiple authentication schemes registered.
We need to handle such a scenario because we have an ASP.NET core 2.2 app exposing some action methods (we use the MVC middleware) that must be used by an angularjs SPA which relies on cookies authentication and some third parties applications which use an authentication mechanism based on the Authorization HTTP request header. Please notice that the involved action methods are the same for both the users, this means that each one of them must allow authentication using both the cookie and the custom scheme based on Authorization HTTP request header. We know that probably this is not an optimal design but we cannot modify the overall architecture.
This documentation seems to confirm that what we would like to achieve is entirely possible using ASP.NET core 2.2. Unfortunately, the cookie authentication used by the UI app and the custom authentication used by the third parties must behave differently in case of an authentication challenge and their expected behaviors are not compatible with each other: the UI app should redirect the user to a login form, while a thir party application expects a raw 401 status code response. The documentation linked above does not offer a clear explanation of the ChallengeResult handling, so we decided to experiment with a test application.
We created two fake authentication handlers:
public class FooAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Foo failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status403Forbidden;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
public class BarAuthenticationHandler : IAuthenticationHandler
{
private HttpContext _context;
public Task<AuthenticateResult> AuthenticateAsync()
{
return Task.FromResult(AuthenticateResult.Fail("Bar failed"));
}
public Task ChallengeAsync(AuthenticationProperties properties)
{
_context.Response.StatusCode = StatusCodes.Status500InternalServerError;
return Task.CompletedTask;
}
public Task ForbidAsync(AuthenticationProperties properties)
{
return Task.CompletedTask;
}
public Task InitializeAsync(AuthenticationScheme scheme, HttpContext context)
{
_context = context;
return Task.CompletedTask;
}
}
We registered the authentication schemas inside ConfigureServices method as follows:
public void ConfigureServices(IServiceCollection services)
{
services
.AddMvc()
.SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(options =>
{
options.DefaultChallengeScheme = "Bar";
options.AddScheme<FooAuthenticationHandler>("Foo", "Foo scheme");
options.AddScheme<BarAuthenticationHandler>("Bar", "Bar scheme");
});
}
This is our middleware pipeline:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseMvc();
}
and finally we created a controller with an action method requiring authentication:
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
// GET api/values/5
[HttpGet("{id}")]
[Authorize(AuthenticationSchemes = "Foo,Bar")]
public ActionResult<string> Get(int id)
{
return "value";
}
}
We noticed that:
both the FooAuthenticationHandler and BarAuthenticationHandler are called to handle the ChallengeResult
the order is FooAuthenticationHandler before BarAuthenticationHandler and depends on the Authorize attribute (if you swap the authentication schemes inside the Authorize attribute then BarAuthenticationHandler is called first)
the caller gets a raw 500 status code response, but this only depends on the order in which the authorization handlers are called
the call to options.DefaultChallengeScheme = "Bar"; matters if and only if inside the [Authorize] attribute the property AuthenticationSchemes is not set. If you do so, only the BarAuthenticationHandler is called and FooAuthenticationHandler never gets a chance to authenticate the request or handle an authentication challenge.
So, the question basically is: when you have such a scenario, how are you expected to handle the possible "incompatibility" of different authentication schemes regarding ChallengeResult handling since they get both called ?
In our opinion is fine that both have a chance to authenticate the request, but we would like to know if it is possible to decide which one should handle the authentication challenge.
Thanks for helping !
You should not specify the schemes on the Authorize attribute.
Instead, specify one scheme as the default, and setup a forward selector.
The implementation of the selector depends on your case, but usually you can somehow figure out which scheme was used in a request.
For example, here is an example from the setup of an OpenID Connect scheme.
o.ForwardDefaultSelector = ctx =>
{
// If the current request is for this app's API
// use JWT Bearer authentication instead
return ctx.Request.Path.StartsWithSegments("/api")
? JwtBearerDefaults.AuthenticationScheme
: null;
};
So what it does is forward challenges (and well, everything) to the JWT handler if the route starts with /api.
You can do any kind of checks there, headers etc.
So in this case OpenID Connect and Cookies are setup as defaults for everything, but if a call is received that is going to the API, use JWT authentication.
The example here forwards all the "actions" you can do with authentication (challenge, forbid etc.).
You can also setup forward selectors for just challenges etc.

Authorization by a Claim of a Role on Web API using JWT Token- Asp.net Core Identity

I have been learning Asp.Net Identity on the past few days, I am familiar with authorizing the controller with [Authorize(Roles = "Admin")] or [Authorize(Policy = "OnlyAdminAndModerators")] for example.
I am using JWT token, when authorizing via "[Authorize(Roles = "Admin")]" all I have to do is set a role type on my token, like this:
{
"nameid": "a173e923-1808-4d7d-2b64-08d684882677",
"unique_name": "yuri",
"role": [
"Admin",
"Moderator"
],
"nbf": 1549522727,
"exp": 1549609127,
"iat": 1549522727
}
With this, my controller is able to authenticate via the "role" name on the json and the value of "Admin".
What I have heard is that it is possible to create a role on the Identity AspNetRole Table, associate a claim to the role via the AspNetRoleClaims table, so for example Admin would have "CanAdd" claim, then on the Startup class, I could create a Policy saying something like options.AddPolicy("Add Role", policy => policy.RequireClaim("CanAdd", "AddClaim"));
And then finally I could go on my controller, set a method with [Authorize(Policy = "Add Role")] and the controller would authorize any user with the Role of Admin because he would have the CanAdd claim.
Sorry I know it's a big question but I really want to make this work.
Thanks in advance.
One way to get additional claims retrieved based on the contents of your token can be done in an message handler that runs after the reading of the token and before the authorization step. For .NET Full framework I used OWin to do this. This block injects additional claims into the claimsPrinciple that can be used then in the policies you define.
This is my startup file:
ConfigureAuthorization -> my extension method to wrap tge BearerTokenAuthentication owin block
IncludeAzureActiveDirectoryUserClaims -> get claims from Azure APi and add them...
using Owin;
[assembly: OwinStartup(typeof(Token.API.Startup))]
namespace Token.API
{
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.ConfigureAuthorization(ClaimsProviders
.InitializeAuthorizationProviders()
.IncludeAzureActiveDirectoryUserClaims()
);
}
}
}
If I would do it for .NET Core , it would look something like this:
Bearer Authentication: link
In startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
{
app.UseAuthentication();
app.Use(async (context, next) =>
{
//Retrieve claims from database based on roles in token.
// Add to loaded identity (= context.User)
await next.Invoke();
});

How are middlewares executed in ASP.NET Core

I'm adding Auth0 to simple project and trying to understand how middlewares work.
In my Startup.cs I have this code
public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory, IOptions<AuthSettings> auth0Settings)
{
loggerFactory.AddConsole(Configuration.GetSection("Logging"));
loggerFactory.AddDebug();
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
// Add the cookie middleware
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
// Add the OIDC middleware
var options = new OpenIdConnectOptions("Auth0")
{
// here there are some configurations
// .....................
};
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("name");
options.Scope.Add("email");
options.Scope.Add("picture");
app.UseOpenIdConnectAuthentication(options);
app.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}");
});
}
If I understand correctly the idea of middleware in ASP.NET Core in our example, if there is a cookie present and authentication can be done by it
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true,
AutomaticChallenge = true
});
OpenId middleware will not be executed.
app.UseOpenIdConnectAuthentication(options);
Could somebody explain me how does OpenId middleware knows that it should not be executed?
At the bottom we have
app.UseMvc(routeBuilder =>
{
routeBuilder.MapRoute("Default", "{controller=Home}/{action=Index}");
});
How does it knows that it should always be executed but in case where we request some static file we do not use mvc.
Every single middleware in the pipeline can choose to call the next middleware. The reason you get static files instead of it hitting an MVC controller is because the static file middleware finds the file requested, and chooses not to call the next middleware in the chain. It simply returns the file as a response.
AutomaticAuthenticate in authentication middleware always means "Inspect the incoming request. If you find something that interests you, create a ClaimsPrincipal from it." In this case cookie authentication automatically creates a principal for the signed-in user when their sign-in cookie is in the request, before passing the request to the next middleware.
The OpenId Connect middleware executes actually, but it doesn't do anything because it won't find anything interesting in the request even if it had AutomaticAuthenticate = true. It is looking for requests to its callback path, which by default is set as CallbackPath = new PathString("/signin-oidc"); in the constructor.
The two authentication middleware are setup like this so that the cookie middleware runs always, but OpenId Connect only redirects to the identity provider when requested (e.g. by returning a ChallengeResult from your MVC controller).

MVC 4 OWIN application using session store for Cookie authentication results in Thread.CurrentPrincipal != HttpContext.User

In an OWIN MVC application, I am trying to preserve the BootstrapContext.SecurityToken between requests to allow for creating an ActAs token for federation.
By default the Cookie serialization tries to preserve the BootstrapContext.Token (string form) and ignores the BootstrapContext.SecurityToken (decoded token), this sort of makes sense, avoiding the exposure of the decoded token?
I have therefore tried to use the CookieAuthenticationOptions.SessionStore to preserve the whole BootstrapContext server side. This (sort of) works but with the problem as described below!
Background:
I have lifted the AspNetAuthSessionStore implementation from the sandbox code in the Katana source repository [http://katanaproject.codeplex.com/] which functions as expected saving and restoring the ticket to the HttpContext.Session.
I also lifted the following extension code:
public static class AspNetSessionExtensions {
public static IAppBuilder RequireAspNetSession(this IAppBuilder app) {
app.Use((context, next) => {
// Depending on the handler the request gets mapped to, session might not be enabled. Force it on.
HttpContextBase httpContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
httpContext.SetSessionStateBehavior(SessionStateBehavior.Required);
return next();
});
// SetSessionStateBehavior must be called before AcquireState
app.UseStageMarker(PipelineStage.MapHandler);
return app;
}
public static IAppBuilder UseAspNetAuthSession(this IAppBuilder app) {
return app.UseAspNetAuthSession(new CookieAuthenticationOptions());
}
public static IAppBuilder UseAspNetAuthSession(this IAppBuilder app, CookieAuthenticationOptions options) {
app.RequireAspNetSession();
options.SessionStore = new AspNetAuthSessionStore();
app.UseCookieAuthentication(options, PipelineStage.PreHandlerExecute);
return app;
}
}
and use the registration functions in my OWIN Startup.
Problem:
In my MVC 4 target action I check the following:
var ctxtuser = HttpContext.User as ClaimsPrincipal;
var claimsPrincipal = Thread.CurrentPrincipal as ClaimsPrincipal;
With the straight cookie implementation (no session store):
app.UseCookieAuthentication(new CookieAuthenticationOptions() {});
the two principals are the same and are the expected value (but without the BootstrapContext content!).
If I simply try to add the Session store to this implementation:
app.UseCookieAuthentication(new CookieAuthenticationOptions() {
SessionStore = new AspNetAuthSessionStore()
});
it fails due to there being no session to store into! Presumably because we aren't allowed a session at Authenticate stage.
If I use the registration from the extension above:
app.UseAspNetAuthSession(new CookieAuthenticationOptions() {
});
version then the code functions almost as expected except that the Thread.CurrentPrincipal is not equal to the HttpContext.User by the time it hits my action. The context user is my claims principle but the Thread.CurrentPrincipal is set to an annonymous WindowsPrincipal.
I have debugged through the code and the claims principal is being applied to both the thread and the context in:
Microsoft.Owin.Host.SystemWeb.OwinCallContext.SetServerUser
But somehow the Thread version gets set back to the annonymous Windows user before hitting my action??
I assume this has something to do with the specific pipeling stage setting in
app.UseCookieAuthentication(options, PipelineStage.PreHandlerExecute);
as opposed to the default value (PipelineStage.Authenticate) used when I request
app.UseCookieAuthentication(new CookieAuthenticationOptions()...
I am wondering if the default code setting the principal at the Authenticate stage makes it 'Stick' as the one ASP.Net wants to use, waiting until PreHandlerExecute is too late?
But can't see how to fix this.
What am I doing wrong?

Categories