Best method to identify the reason for an [Authorize] policy failure? - asp.net-core

Without creating custom authorization handlers, is there a way in asp.net core to detect the reason that an action fails with a 401 Unauthorized and return a semantically helpful response?
Specific example: User has the role "Student". Action has an attribute specifying [Authorize(Roles = "Teacher")]. When User attempts to load Action, a policy failure results in an error view being redirected to via the authentication middleware.
Ideally I would be able to identify the reason within the error handling action method and provide a specific response stating that the requested URL is only available for Teachers, but so far nothing obvious has presented itself. I can identify which authentication schemes were involved, and which roles the user is in, but it's unclear if I can tell which roles are required for the action.

Related

How to force ValidatePrincipal to be called on AuthenticatedUsers even for [AllowAnonymous] actions?

I am using cookie authentication on a new ASP.NET CORE 3.1 project.
I have a controller action that is used by both guests as well as authenticated users. Because of that, it is decorated with the AllowAnonymous attribute.
The behavior of the action is slightly different if the user is authenticated so I use httpContext.User.Identity.IsAuthenticated to check that out and if true, I then retrieve the principal's claims in order to perform a database update.
The problem that I have is that because the action allow's anonymous, the OnValidatePrincipal event of the cookie authentication scheme is not called to make sure that the current claims are up to date.
This means that even if the httpContext.User.Identity.IsAuthenticated flag is true, I cannot rely on the claims that come with it because they are not validated in this case.
First of all, this seems to me like a problem. Second, does anyone know if there is a way around that ? Some ways to force the OnValidatePrincipal event to be called as soon as the httpContext.User.Identity.IsAuthenticated flag is true no matter if the action requires authorization or not ?
You can try creating a dummy controller method that requires authorization and call it when httpContext.User.Identity.IsAuthenticated flag is true.

WebAPI how to disable another user from the admin

I have created a simple WebAPI in Visual Studio C# to act as my backend to Angular.
Users can register via the Angular frontend and passed to a controller in the WebAPI to register that user in the backend DB (MSSql). All fine.
I am using token authentication from Angular to my API and using claims and roles to verify the user is logged in and has access to the requested controller.
How, as an Admin, can I disable another user so they are instantly locked out?
An example would be: A rogue user starts abusing the Angular application and I need to lock them down instantly and not to wait until their token expires and they are required to login again.
I could do a check in each and every controller to lookup the user in the DB and check their status and if I have set their status to "locked-out" then return say a 403 forbidden status from the controller but this seems a lot to lookup the user for each DB request they make.
Is there a "dot-net" way of doing it? I would love to be able, as the admin, to instantiate the said user, change their claim value for "status" to "locked-out" and I simply check their claim when making api requests but I can't see a way of changing another users claims.
Any ideas?
Thank you.
Expanding on my comment above, we have a handler in our pipeline that handles Authorization. We do look at that Authorization header and parse it but all of the controllers rely on the already-parsed value that we hang in the Properties collection. That way all AuthZ/AuthN occurs in this handler and whatever the handler sets in the Properties collection is what all of the application sees.
In your case the handler would need to be able to check whatever throttle or lockout you are using and replace the actual claims received with your "user is locked out" claims instead.

ForbidAsync Vs ChallengeAsync why and when to use them

There are two method on AuthenticationManager class, ForbidAsync() and ChallengeAsync(), I know that I can execute HttpContext.Authentication.ForbidAsync or return a result of type ForbidResult in my controller and it has the same effect, same is true for ChallengeAsync. But it seems that they produce the same result:
public ForbidResult ForbidResult()
{
return Forbid();
}
public ChallengeResult ChallengeResult()
{
return Challenge();
}
There are not much documentation on the use of them or any example at this point, I was wondering how and why to use them.
Update: By the way, I complied my research in this area to an article by the name of Asp.Net Core Action Results Explained.
A challenge result should generally be used in cases where the current visitor is not logged in, but is trying to access an action that requires an authenticated user. It will prompt a challenge for credentials. It could also be used for an authenticated user, who is not authorised for the action, and where you want to prompt for higher privileged credentials.
A forbid result should be used in cases where the current visitor is logged in as a user in your system, but is trying to access an action that their account does not have permission to perform.
With the standard ASP.NET Core CookieAuthentication added by Identity, default paths are set to handle each case and the user gets redirected.
By default...
Access denied - i.e. forbidden looks to redirect to /Account/AccessDenied
Unauthenticated - i.e. challenge looks to redirect to /Account/Login
Without redirection, forbidden will return a 403 status code, challenge will return a 401.
In your case, as redirects are occurring as specified in the default options, you're seeing the 302 found status codes instead.
I've not looked deep into code around this, but that's my general understanding.

JwtBearerAuthentication doesnt return 403 Forbidden, always returns 401 Unauthorized

If ClaimsIdentity set through JwtBearerAuthentication middleware doesnt have enough roles required through
[Authorize(Roles="whateverrole")]
it returns 401 instead of 403.
I am struggling with this in asp.net core web api whole night. I have also seen this question on stackoverflow but i havent seen any solution i could make work. The order of registering middleware nor AutomaticChallange setting did the job.
I dont know if i am missing something but it seems shocking that this hasn't been solved properly for years. It is so annoying.
Is there any normal, usual, non-workaround, non-hack way of solving this?
UPDATE (in response to comment from #juunas)
I have tried that and roles are mapped correctly from Claims.
So, if i remove Roles requirement from attribute, for all roles that user is assigned to (in JWT token) User.IsInRole(x) returns true. So mapping works just fine.
About moving from roles based authorization to policies...can you provide some some link with some best practices, recommendations or something that you base that statement on?
I am not saying its not something to be done but would just like to understand it.
It's important to understand the difference in these to errors to understand why you will get one and not the other.
401 is for authentication. If you are getting this error then you have to ask yourself is the user logged in, or does the user have a current token provided by a valid token provider? If the token has expired or the provider is not valid then you can get a 401. If this is what you are getting then you need to check User.Identity.IsAuthenticated does this return true? Most likely it returns false.
403 is for authorization. This would mean the user has a valid token, but the token they have does not give them access to the resource they are requesting. This is where you would want to check User.IsInRole() and it would return false.
If you're getting 401 it means that the user hasn't been authenticated i.e. they have not logged in, their login was invalid, the token has expired... etc. As far as your application is concerned the user hasn't proved they are who they say they are yet.
Edit: Apologies for assuming the user wasn't Authenicated, I didn't see where you stated that they where in your first post. It's hard to help without seeing code but my next guess is that the Claims check hasn't been added to the services pipeline.
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc();
services.AddAuthorization(options =>
{
options.AddPolicy("whateverrole", policy => policy.RequireClaim("whateverrole"));
});
}
This is in your Startup.cs. MS doc is here https://learn.microsoft.com/en-us/aspnet/core/security/authorization/claims
Last update: Simply put using the default Authorize attribute tag you can't change it. MS designs I this way due to the number of layers in the pipeline that could impact authentication. I was unaware of this because I use a custom Authorize attribute and forgot that I over wrote the way it handled status codes.
However I found a nice solution that might suite your needs https://github.com/aspnet/Security/issues/872#issuecomment-232624106
It adds an error page to the pipeline prior to the app.UseMvc() that redirects authentication errors to an error page that returns the correct status code.

Standard Response of a Login/Sign In API

While making an Login/SignUp RESTful API, should the response contain any other user information e.g. User Profile, Permissions etc?
When I've done this previously the Login returns a token for the new session and possibly the userid. Then I've had other services that I call with this token to get a list of permissions etc. You should make each resource do a single thing, which allows greater flexibility and reuse. Like everything though... this is my opinion on it but there is no right or wrong way to do it.
The REST principle is that when you POST a request to the server, it should respond with a status to indicate success or failure (i.e. 201 created) and a pointer to the new resource just created.
In my comments above the resource being created is the session and the token is the identifier of that resource.