What could cause `UserManager` to return the wrong user? - asp.net-core

Something rather scary is happening on my ASP.NET Core 2.1.0 MVC site. While I was browsing, all of a sudden it shows I am logged in as a different user (who also happens to be browsing the site at that time).
I can't pinpoint whether there is a specific use case that triggers this, but this has happened twice now. Navigating to other pages still shows I am logged in as the other user. It even seems I take over the claims of the user I am incorrectly logged in as.
My question is: what could make this happen?
EDIT: I have since changed userManager and notificationService to 'scoped' and this issue occurred again, thus the potential issue reported here cannot be the cause.
Trying to look into this, I believe the culprit might be the following call in _Layout.cshtml:
#inject UserManager<ApplicationUser> userManager
#inject NotificationService notificationService
#inject CommunityService communityService
#{
ApplicationUser user = await userManager.GetUserAsync( User );
}
The returned user is used to show user information and do calls to notificationService and communityService. These were also showing data pertaining to the incorrect (not me) user.
If it matters, this is how ApplicationDbContext is set up in Startup.cs:
// Add framework services.
services
.AddDbContext<ApplicationDbContext>( options => options
.UseLazyLoadingProxies()
.UseSqlServer(_configuration.GetConnectionString( "DefaultConnection" ) ) );
services
.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
I recalled that 'scoped' is the recommended lifetime to use when registering Entity Framework for dependency injection. Both NotificationService and CommunityService, however, are registered as 'transient' and request ApplicationDbContext through constructor injection to access data.
services.AddTransient<CommunityService, CommunityService>();
services.AddTransient<NotificationService, NotificationService>();
Could this have anything to do with it? Currently, I do not understand whether this could make any difference. I cannot seem to replicate this issue.

Good news! I was causing this myself (I believe, help me figure this out by reading the details below). You can thus rest assured that unless you are making the same mistake as I am, the ASP MVC authentication mechanism is not to blame here (at least, that is my current understanding).
I will document what exactly I did wrong, and how to replicate, since others might possibly make the same mistake.
In short: I called SignInManager<TUser>.RefreshSignInAsync(TUser) with the 'wrong' user (the one I ended up being logged in as), causing me to be logged in as that user.
Why did I do this? In my specific use case, I wanted to hand out a claim to another user based on the action of the currently logged in user. I therefore called:
await _userManager.AddClaimAsync( userToGiveClaim, newClaim );
await _signInManager.RefreshSignInAsync( userToGiveClaim );
I called RefreshSignInAsync since I wanted to prevent the user who had been given the claim from having to log out and in for the new claim to go into effect. From the RefreshSignInAsync documentation I got the impression this should work:
Regenerates the user's application cookie, whilst preserving the
existing AuthenticationProperties like rememberMe, as an asynchronous
operation.
Parameters
user The user whose sign-in cookie should be refreshed.
I'm still not entirely clear why the user that is currently logged in when this call is triggered gets the identity of the user passed to this call. That is still not how I understand the documentation (I filed this as a bug report), but since this is reproducible I am now more inclined to believe I simply misunderstood the documentation.

Related

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.

Using User.IsInRole returns random result when UserRole changes

I'm using asp.net core 2.1 with default identity settings and every time a role is changed the user should re-login to see the role changes.
If I add the following settings, roles should be updated on each request, the Authorize attribute works well but User.IsInRole() method returns random results on each request.
services.Configure<SecurityStampValidatorOptions>(options =>
{
options.ValidationInterval = TimeSpan.Zero;
});
What's the problem with User.IsInRole()? How to fix it?
Edit:
I was incorrect about the Authorize attribute behavior, that works random too.
Edit:
a test project to reproduce the issue (warning: it's using InMemory db) -> https://github.com/ans-ashkan/AspNetCoreUserIsInRoleTest
http://localhost:5000/users/getall -> 200: ["test"]
http://localhost:5000/users/signin?username=test&password=123 -> 200
http://localhost:5000/users/isinrole?role=admin -> {"isInRole":false,"identityName":"test"}
http://localhost:5000/users/adduserrole?username=test&role=admin -> 200
http://localhost:5000/users/isinrole?role=admin -> {"isInRole":**random true or false**,"identityName":"test"}
http://localhost:5000/users/signout -> 200
http://localhost:5000/users/signin?username=test&password=123 -> 200
http://localhost:5000/users/isinrole?role=admin -> {"isInRole":true,"identityName":"test"}
Edit:
Issue link on AspNet/MVC repo
I’ve investigated this problem in detail and posted my findings in the issue. It is indeed a bug, but it actually does not have that much of an effect on your problem:
Changing a user’s roles will not invalidate the user’s security stamp, so the issued claims identity that the user receives through the cookie is not actually invalid. So if you want to invalidate the identity when the roles change, you will have to look for a different solution.
However, in my opinion, you are completely misusing this: If you want to refresh the identity and its claims on every single request, then there is really no point in actually having a claims identity at all. Running the authentication stack is not free, and having to run the whole pipeline of validating and reissuing will be quite expensive. And when you don’t actually want to store the identity for a longer time anyway (because you invalidate it right in the next request), then that’s really wasted work.
So if you really need a permission system that are absolutely sharp and updated right in the moment when they change, then consider using something different. You could set up a separate database and just store the “roles” there, and then, when you access something protected, you just fetch the user’s roles on demand there to verify access. That will also help you from fetching the roles all the time on every request since now you would only fetch it when you need it.
Of course, you don’t need a separate database for this. You could also use the built-in roles of Identity. You just need to remember then though that the role claims are not always the source of truth, so you should always load the user’s roles from the database (through the user manager).
You can actually design this pretty well with ASP.NET Core’s authorization stack. For example, you could create a requirement for a role and then implement an authorization handler that checks the role by going through the database. That way, you can make this as transparent as using role claims for users. E.g. you could just use the same Authorize attribute which you would be using otherwise.

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.

IdentityServer4 - How to log in user following password reset if existing auth cookie exists?

I'm attempting to extend my Identity Server 4 implementation and provide an in house password reset feature. I've completed the entire password reset process however I'm running into a situation upon redirecting the user back to the client application that's causing me grief.
If there is a valid existing authentication cookie, for instance if a previous user doesn't initiate a proper log out, when I redirect the current user to the client application, the middle ware uses the existing cookie to build the principal which inevitable ends up with the current user being treated as the previous user, which makes sense.
In vain I attempted to make a call to sign in the current user post password reset in Identity Server via await HttpContext.Authentication.SignInAsync(...) as shown during login on their samples here with no change in results.
It seems as I'm missing something fundamental in how to properly reproduce the sign in process from within my password reset process.
Is there any way from Identity Server that I can effectively sign in the current user post password reset and force the client application to invalidate/ignore the existing authentication cookie?
Update: After thinking about this over the weekend, I guess this broaches a much broader subject in that how far does one go in ensuring the invalidation of existing auth cookies? The fact is that because of the way cookie authentication works, as long as that cookie remains valid we are under the assumption that we are is still receiving requests from the original physical person.
One might argue that this is a good case for reducing the expiration time of the cookie, but that's a slippery slope because on one hand if the expiration is too short, usability can be affected making for a less enjoyable experience and unhappy users. Make the expiration too long and that window where a valid auth cookie is sitting on the browser idle with no meat and bones behind it grows, and all it takes is someone else to browse to the site to proceed unhindered and authenticated as someone else. This would be most evident in a situation with shared computers where many people need to access the same app.
I suppose a secondary question would be to what extent is the onus on the user to initiate a proper logout process, and to what lengths should I go to to ensure any existing auth cookies are invalidated?
After a few conversations with co workers, the consensus was that it is the responsibility of the user to ensure they are properly logged out in any public/computer sharing situations and that we could not guarantee without a reasonable doubt any previously existing authentication cookies are cleaned up without greatly affecting user experience and basic site functionality.
For due diligence we however will provide some manner of attempting to clean up any existing authentication cookies by doing a simple check and redirect on the default route of the client application. This solution has nothing to do with identity server, and is not fool proof, but in conjunction with tuning the session expiration times should prove effective for daily users.
[AllowAnonymous, Route("")]
public async Task<IActionResult> Index()
{
if (User.Identity.IsAuthenticated)
{
await HttpContext.Authentication.SignOutAsync(...);
HttpContext.User = new ClaimsPrincipal(new ClaimsIdentity());
}
return RedirectToAction(nameof(Welcome));
}
[Authorize]
public IActionResult Welcome()
{
return View();
}

What OWIN Middleware Redirects After User Grants Client?

I've looked hard into this article about OAuth Authorization Server with OWIN/Katana: http://www.asp.net/aspnet/overview/owin-and-katana/owin-oauth-20-authorization-server
The article does tell us how to set up a basic Auth server but seems to omit a lot of information and code. I'm particularly interested in the implicit grant flow. They did provide the login page and the "permissions" page, but I'm confused:
Where is the code that decides whether the authenticated user has granted the client? This can't be done "behind the scenes" because we NEVER told any middleware "component" the path "/OAuth/Authorize".
Where is the code that actually redirects the user back to the client's website, along with the auto-generated access_token and other values?
I'm suspecting that there is a proper way to "construct" the ClaimsIdentity object (particularly the scope claims) before passing it to authentication.SignIn(claimsIdentity) in /OAuth/Authorize, so that it would automatically redirect the user back to the client with access and refresh tokens.
The MVC Actions of /OAuth/Authorize and /Accounts/Login seem to always return View() even after successful authentication and granting, thus never forwards the user back to the client's website. This seems like I would have to manually determine when to return Redirect(Request.QueryString["RedirectUrl"]);, and figure out the encrypted values to pass along with it. This doesn't seem like I should be generating the exact response.
What did I overlook?
As #(LittleBobby Tables) said your questions are very broad.
Based on how you asked the question you actual understand the topics but not the how?
I suggest you look at the full source code at
http://code.msdn.microsoft.com/OWIN-OAuth-20-Authorization-ba2b8783/file/114932/1/AuthorizationServer.zip
Your answers are either present or will lead you in the right direction