In my Program.cs I have
app.UseRouting();
app.UseCors();
app.UseAuthentication();
app.UseMiddleware<MyCustomMiddleware>();
app.UseAuthorization();
app.UseEndpoints(cfg =>
{
cfg.MapControllers();
cfg.MapFallbackToController("Get", "Branding");
});
with the expectation that my custom middleware would run after authentication and therefore I would have a claims identity to play with.
But when my MyCustomMiddleware.Invoke is called the _next is AuthorizationMiddleware as expeted, but the context.User is not authenticated and has no claims.
However, the context.User is authenticated and has claims after _next.Invoke(context) returns.
So it looks like the middleware is running out of order.
What could be wrong?!
I believe that the behaviour you are seeing is correct if the AuthenticationMiddleware has yet to complete authenticating the user. Depending on your selected authentication scheme, the AuthenticationMiddleware may have returned back a HTTP 401 Challenge response to the client, or a HTTP 301 redirect to send the user to the authentication provider's login page if using OAuth. Check the context.Response inside of your middleware to be sure that this is in fact what is happening.
If the AuthenticationMiddleware hasn't short circuited the middleware, the unauthenticated request will still be passed on to the subsequently registered middleware before the response is sent back to the client.
Typically, its recommended that the AuthorizationMiddleware is ordered immediately after the AuthenticationMiddleware (see: ASP.NET Core Fundamentals Built-in Middleware), so that the user is properly authenticated and claims have been added. Because you have chosen to put a middleware in between, you are seeing the in-between process that ASP.NET core is following to authenticate and provide claims for the user.
Related
I feel like this question has been asked several times before but I'm not getting clued in or finding out what I need to know.
I have an Azure AD with two tenants. Tenant 1 has all our users in it. Tenant 2 is meant for external users making inbound api calls - we are in the proof of concept stage right now.
All works great when I set up new users in tenant 2. The users are created, the app is created, the scopes for the api are defined and finally using the enterprise app, roles are created with local users assigned to the roles. The .Net Core app is decorated with [Authenticate Roles("Read, Write"], etc. The scopes are then validated using the HttpContext.VerifyUserHasAnyAcceptedScope(scopeRequiredByApi); inside each controller method. As I said, it all works great with users who have been defined as local users in Tenant 2.
If it helps, we are using Postman to test with.
However, if I modify the login call to simply use the guest user, which an existing user in the Azure AD client 1 tenant I see the following behavior. The user is Authenticate and a user_token as well as an id_token is returned. Calling the existing PUT and POST API methods always returns a 403 Forbidden. I had read that a guest id should use the auth_token so that is what I have done but to no avail.
If anyone has any advice on where I should look, if I am overlooking something simple or if there is a config value I'm not making I'd love to hear it. I realize I'm low on actual code snippets but there really aren't any. The API works just fine and the Postman call work great for local users. It's simply the guest users that aren't getting assigned to a "proper" role.
EDIT - the appsettings.json file relevant bits:
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxxx",
"Domain": "tenant01.onmicrosoft.com", // the domain for tenant 1
"TenantId": "common"
},
And the startup.cs file:
public void ConfigureServices(IServiceCollection services)
{
services.AddMicrosoftIdentityWebApiAuthentication(Configuration);
services.AddControllers();
// some other code...
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseHttpsRedirection();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
The auth token for the guest user does show the scope but the role is missing. The id token for the guest user simply shows the standard user claims - Name, preferred_username, the issuing tenant, etc.
I'm missing something that's probably not terribly difficult to set up but I've no idea what to do from here.
We figured this out. No changes were made to the code or the json file. The URL used to call the authentication was modified. The URL that was changed from:
https://login.microsoftonline.com/oauth2/v2.0/token
TO this url
https://login.microsoftonline.com/< tenantid >/oauth2/v2.0/token
This change caused the role to appear in the id_token. I had read that the id_token needed to be used but I was not aware of the change that needed to be made to the url to include the tenant id.
We have a Gateway (implemented using Ocelot), which performs both Authentication & Authorization of the calls before it reaches the APIs
For Authentication, the gateway uses JwtBearer like below
services.AddAuthentication(Microsoft.AspNetCore.Authentication.JwtBearer.JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Events = JwtBeaerEvents();
options.TokenValidationParameters = TokenValidationParameters(tokenConfig);
});
And, this validates the token correctly.
Apart from this, the Gateway is implemented with Custom Authorization, to which it reads the permission related settings using a custom configuration file. And, this Custom Authorization is added as a middleware
We try to add this Authorization middleware after Authentication middleware, like
app.UseAuthentication().UseAuthorizationMiddleware();
This works for a valid token. However, for an invalid token, irrespective of Authentication got failed, the call is being routed to AuthorizationMiddleware as well. And, based on these findings, looks like we need to go with DI, rather than middleware. But, what we want is a custom implementation for Authorization which accepts the permissions/policy/scope via config file (in the gateway) along with JwtBearer scheme, rather than decorating them in the API attribute. Could anyone throw some light on how to achieve the same?
Your help is much appreciated
The issue is due to the behaviour of .net core. When the Identity's IsAuthenticated flag is false, Http StatusCode is not set to 401 by the framework in case of Token validation failure during Authentication and also it proceeds to the next call. If only we used the Policy based Authorization, it would have been automatically taken care by RequireAuthenticatedUser() while building the Authorization Policy. However, since we are using a custom middleware, introduced one another middleware which replicates what DenyAnonymousAuthorizationRequirement does, like below
var user = httpContext.User;
var userIsAnonymous =
user?.Identity == null ||
!user.Identities.Any(i => i.IsAuthenticated);
if (userIsAnonymous)
{
httpContext.Response.StatusCode = 401;
return Task.CompletedTask;
}
return _next(httpContext);
We placed this middleware in between Authentication & Authorization middlewares and the issue has been resolved
I've below code in Startup.Configure function
app.UseMiddleware<LogMiddleware>(_loggingLevelSwitch)
//other code
.ConfigureExceptionHandler(Logger, Configuration)
.UseIdentityServer()
.UseStaticFiles()
.UseMiddleware<ContextSetupMiddleware>()
.ConfigureMvcAndLocalization()
//other code
UseIdentityServer internally calls UseAuthentication
I've setup two schemes cookie and Bearer.
My expectation is after authentication ContextSetupMiddleware should be called but it is happening otherwise.
For cookie, I can understand since authentication will be done later and once signin is done it works as expected.
For Bearer, I think it should be part of pipeline since we add a scheme to authenticationbuilder.
Update:
Made defaultscheme as "Bearer" it started working as I wanted.
However the website has AccountLogin screen (cookie based Auth) and some RestAPI's (JWT based Auth).
Is there a way cookie based authentication is only applicable on AccountLogin screen. I can't add [Authorize] since it is login scree.
It is actually by design. The default scheme will override if at any middleware it is required then use context.AuthenticateAsync().
In my case, in ContextSetupMiddleware just call context.AuthenticateAsyc() and authentication done.
I would like to know whether I am
(a) engaging in good coding practices,
(b) repeating myself harmlessly, or
(c) adding inefficient redundancies
For example:
1) In Configure() I can add RewriteOptions().AddRedirectToHttps();
2) In ConfigureServices() I can add
services.Configure<MvcOptions>(options =>
{ options.Filters.Add(new RequireHttpsAttribute()); });
Another example:
1) In Configure() I can add app.UseAuthentication();
2) In ConfigureServices() I can add .RequireAuthenticatedUser() to my AddMvc() call.
It seems in both examples that I can get away with just one call. Am I free and clear to keep only one call? And if so, which one is the better to keep?
I've searched around a fair bit and I can see all of these approaches in use, but I haven't found a resource that compares the relative merits of these calls, let alone indicates whether it's good or bad practice to use them together.
To take your first example:
1) In Configure() I can add RewriteOptions().AddRedirectToHttps();
2) In ConfigureServices() I can add services.Configure<MvcOptions>(options =>
{ options.Filters.Add(new RequireHttpsAttribute()); });
Both of these achieve essentially the same thing - they will redirect HTTP requests to HTTPS. The difference is which requests they are applied to.
If you use the rewriter middleware, all requests that make it to the middleware will be redirected to HTTPS.
public void Configure(IApplicationBuilder app)
{
app.UseStaticFiles(); // Requests handled by this middleware won't be redirected to HTTPS
var options = new RewriteOptions()
.AddRedirectToHttps();
app.UseRewriter(options); // All requests that make it this far will be redirected from HTTP to HTTPS
app.UseMvc(); // Requests guaranteed to be HTTPS
}
In the second case, where you use a global filter to apply the RequireHttpsAttribute, only requests that make it to the MvcMiddleware will be redirected to HTTPS.
In terms of best practices, I recommend using the rewriter middleware - you can add it to the start of your middleware pipeline, and then all of your requests are required to HTTPS, instead of just the requests that make it to the MVC middleware.
In your second example, the two methods actually do different things:
app.UseAuthentication() - authenticates the request, and sets the User associated with the request by e.g. deserializing the user principal stored in the cookie
RequireAuthenticatedUser() - Requires that a User has logged in before action methods on your controllers are called. If the user hasn't logged in, they are redirected to the login page. In this case, you must call app.UseAuthentication() before app.UseMvc(), otherwise the User for the request will not be set even if you've logged in, and you will be redirected to the login page.
I have a standard VS2013 MVC5 project with a Web Api 2 in it. The way the standard project is designed, the [Authorize] attributes simply return a 401 status code if the request is not authenticated, while a totally separate module sniffs for any 401 codes, halts them, and instead sends a 302 redirect to the login page specified in the Startup.Auth.cs file. That's ok for Mvc controllers, but really stinks for Web Api controllers because for example browsers will automatically redirect ajax requests to the login url, so you ultimately end up with a 200OK status even though the response text is just the html of the login page.
That makes it hard to write good javascript that can distinguish between a case where you just need to tell the user to log back in versus other kinds of errors. Ideally we should be able to tell based on the status code, but javascript never ever sees the 401 status. What is the best way to handle this?
My first thought was to write an authorization attribute but use status code 403 instead of 401:
public class ApiAuthorizationAttribute : System.Web.Http.AuthorizeAttribute
{
public override void OnAuthorization(System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext.RequestContext.Principal.Identity.IsAuthenticated)
{
base.OnAuthorization(actionContext);
}
else
{
actionContext.Response = actionContext.ControllerContext.Request.CreateErrorResponse(HttpStatusCode.Forbidden, "Not signed in.");
}
}
}
Of course, specifications explicitly state that 403 is incorrect:
Authorization will not help and the request SHOULD NOT be repeated
My other thought is that maybe I should disable asp.net's 401 redirect module altogether and handle redirects in custom authorization attributes, because even for Mvc views it is lousy because it doesn't allow you to redirect to different login pages depending on where in the site the user is trying to visit.
Are there other, better approaches to handling this?
Here's what I was able to find with a bit more research. The 401 is intercepted by the OWIN middleware. But, OWIN does support branching configurations using the Map method. So in the Startup.cs file I have this:
public partial class Startup
{
public void Configuration(IAppBuilder app)
{
app.Map(new PathString("/api"), site => ConfigureAuth2(site));
ConfigureAuth(app);
}
}
where ConfigureAuth is the default configuration method that comes in the Startup.Auth.cs file, while ConfigureAuth2 is a duplicate of that method but with the LoginPath option left unspecified in the UseCookieAuthentication method, which looks like this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
// Enables the application to validate the security stamp when the user logs in.
// This is a security feature which is used when you change a password or add an external login to your account.
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager))
}
});
According to the documentation, when the LoginPath is unspecified, 401 responses won't be intercepted for this branch.
So with this approach I'm branching all requests into two different configurations--all /api requests get configured not to redirect on 401 statuses, while everything else gets configured to redirect to the login page.
This SO question talked a bit about branching the configuration.
I'm still not sure if this is the best approach though.