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

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.

Related

How do I enforce 2FA in .Net Core Identity?

Question: How can I enforce existing users to set up 2FA in .Net Core 3.1 Identity?
I have seen a couple of answers here already, but I have issues with them as follows:
Redirect user to set up 2FA page on login if they do not have it set up. Problem with this is that the user can simply jump to a different url to avoid this, therefore it is not actually enforced.
Have some on executing filter that checks if the user has 2FA enbaled or not and if not redirect them to MFA set up page. The issue I have with this is that on every single navigation the server must go to the database to check whether the user has this field enabled, thus creating a significant performance hit on each request. I know one trip to the database may not sound like much but I have worked with applications where this was the norm and other things used this method, causing a pile up of pre action db queries. I want to avoid this kind of behavior unless absolutely necessary.
My current idea is to on login:
Check the users credentials but NOT log them in
userManager.CheckPasswordAsync(....)
If the credentials pass, check if the user has 2FA enabled or not. If they do, continue through login flow, if not:
Generate a user token:
userManager.GenerateUserTokenAsync(.......)
and store this along with the username in a server side cache. Then pass a key to the cached items with a redirect to the 2FA setup page, which will not have the [authorize] attribute set, allowing users not logged in to access it.
Before doing anything on the 2FA set up page, retrieve the cached items with the provied key andverify the token and username:
userManager.VerifyUserTokenAsync(......)
If this doesn't pass, return Unauthorized otherwise continue and get the current user from the supplied UserName in the url that was passed via a cache key. Also dump the cached items and key so that should the url be snatched by a dodgy browser extension it can't be used again.
Continue to pass a new cache key to new user tokens and usernames to each 2FA page to authenticate the user as they navigate.
Is this an appropriate use of user tokens? And is this approach secure enough? I'm concerned that having the user not logged in presents security issues, but I think it is necessary in order to avoid the previously mention problem of going to the database on every request to check 2FA, as with this method trying to navigate away will just redirect to login.
I implemented this via a Filter Method
I have a BasePageModel which all my pages inherit
public override async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (!User.Identity.IsAuthenticated)
{
await next.Invoke();
return;
}
var user = await UserManager.GetUserAsync(User);
var allowedPages = new List<string>
{
"Areas_Identity_Pages_Account_ConfirmEmail",
"Areas_Identity_Pages_Account_ConfirmEmailChange",
"Areas_Identity_Pages_Account_Logout",
"Areas_Identity_Pages_Account_Manage_EnableAuthenticator",
"Areas_Identity_Pages_Account_ResetPassword",
"Pages_AllowedPageX",
"Pages_AllowedPageY",
"Pages_Privacy"
};
var page = context.ActionDescriptor.PageTypeInfo.Name;
if (!user.TwoFactorEnabled && allowedPages.All(p => p != page))
{
context.Result = RedirectToPage("/Account/Manage/EnableAuthenticator", new { area = "Identity" });
}
else
{
await next.Invoke();
}
}
I then changed both the Disable2fa and ResetAuthenticator pages to redirect to the main 2fa page
public IActionResult OnGet() => RedirectToPage("./TwoFactorAuthentication");
And removed the reset/disable links from that page
I chose to implement a more modern and OAuth friendly solution (which is inline with .Net Core Identity).
Firstly, I created a custom claims principal factory that extends UserClaimsPrincipalFactory.
This allows us to add claims to the user when the runtime user object is built (I'm sorry I don't know the official name for this, but its the same thing as the User property you see on controllers).
In here I added a claim 'amr' (which is the standard name for authentication method as described in RFC 8176). That will either be set to pwd or mfa depending on whether they simply used a password or are set up with mfa.
Next, I added a custom authorize attribute that checks for this claim. If the claim is set to pwd, the authorization handler fails. This attribute is then set on all controllers that aren't to do with MFA, that way the user can still get in to set up MFA, but nothing else.
The only downside with this technique is the dev needs to remember to add that attribute to every non MFA controller, but aside from that, it works quite well as the claims are stored in the users' cookie (which isn't modifiable), so the performance hit is very small.
Hope this helps someone else, and this is what I read as a base for my solution:
https://damienbod.com/2019/12/16/force-asp-net-core-openid-connect-client-to-require-mfa/
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/mfa?view=aspnetcore-5.0#force-aspnet-core-openid-connect-client-to-require-mfa

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.

Best method to identify the reason for an [Authorize] policy failure?

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.

Adding Custom SignInResults to what's returned from a SignInManager<TUser> PasswordSignInAsync

I want to guarantee that all of my users on sign-in have signed our EULA, and to me, it's similar to the built-in SignInResult.TwoFactorRequired, because I'd like to kick them through the EULA signing process before finalizing their sign-in. Does anyone know of any resources that shows how to create a custom SignInResult so that all of the users of my Identity server will have to follow the same rules on sign-in?
I'd implement a custom SignInManager, but PasswordSignInAsync still returns a concrete SignInResult and I'm not sure if it's possible to wedge in my additional desired states there.
Yeah, you're not going to be able to just override PasswordSignInAsync, but you could create a new method that returns your custom result class and simply hands off the actual sign-in part to PasswordSignInAsync.
However, by the time you get done create derived types, with custom methods and states, and bootstrap everything, it probably is just simpler and more straight-forward to just read the value from the user after sign in, and react accordingly. For example, you can (and probably should) set the EULA acceptance as a claim on the user. Then, you can just do something like:
// sign in user
if (User.FindFirstValue("eula") == null)
{
return Redirect("/eula");
}
Even better, you can create a custom action filter that checks if the user is authenticated, and if so, whether they have the claim. If not, then you redirect to your EULA acceptance page. Then that action filter can be made global in Startup.cs and you don't even need to think about it anymore.

Different Authentication Schemes for the Same ASP.NET Web API

The Situation
I have an upcoming project where the web pages will be making AJAX calls. External clients will also be provided a REST API. I will be implementing this project using ASP.NET MVC 4 with Web API.
I have seen various examples online where people use the [Authorize] attribute for security. I presume this is whenever Web API is called via AJAX on a web page.
I have also seen various examples where an API key was passed along with each request (via query string or header). I presume this is whenever Web API is called from an external system.
The Questions
Here are the questions that immediately come to mind:
Should I be creating a separate controller for internal and external clients?
or should I force the web pages to use the same external authentication model?
or is there a way that external clients can use the Authorize attribute?
or should I somehow support both forms or authentication at the same time?
A Side Note
A colleague pointed out that I might want to deploy the API to a totally different URL than where the web app is hosted. Along the same lines, he pointed out that the external API may need to be more coarse grain or evolve separately.
I don't want to reinvent the wheel, here. This makes me wonder whether I should be using Web API as an internal API for my AJAX calls in the first place or if I should stick to old-school MVC actions with [HttpPost] attributes.
[Authorize] attribute is not meant only for Ajax. When you apply the [Authorize] attribute to say an action method, what this does is it ensures the identity is authenticated before the action method runs,irrespective of the clients making the request and irrespective of the type of credentials submitted to your API. All it looks for is Thread.CurrentPrincipal. Here is the copy-paste of the code from the Authorize filter.
protected virtual bool IsAuthorized(HttpActionContext actionContext)
{
...
IPrincipal user = Thread.CurrentPrincipal;
if (user == null || !user.Identity.IsAuthenticated)
{
return false;
}
...
}
As you can see, all it does it gets the Thread.CurrentPrincipal and checks if the identity is authenticated. Of course, when you include the roles, there are additional checks.
So, what this means is that you will be able to use different means of authentication as long as Thread.CurrentPrincipal is set as a result of authentication. If you have two handlers (or HttpModules in case of web hosting) or authentication filters in case of Web API 2, you can establish the identity based on different factors. For example, you can have a BasicAuthnHandler and a ApiKeyHandler added to config.Handlers and hence run in the web API pipeline one after the other. What they can do is to look for the credentials and set Thread.CurrentPrincipal. If Authorize header comes in the basic scheme, BasicAuthnHandler will authenticate and set Thread.CurrentPrincipal and if the API key comes in, it does nothing and ApiKeyHandler sets Thread.CurrentPrincipal. Both handlers can create same type of prinicipal say GenericPrinicpal or even different one. It does not matter because all the principals must implement IPrincipal. So, by the time Authorize filter runs, Thread.CurrentPrincipal will be set and authorization will work regardless of how you authenticate. Note: If you web host, you will also need to set HttpContext.User as well, in addition to Thread.CurrentPrincipal.