question

bairog avatar image
0 Votes"
bairog asked AgaveJoe commented

ASP .NET Core Identity is not working as supposed in ASP .NET Core 5.0 Razor Pages Web Application

I'm a very new to ASP .NET Core Identity so if some additional info is needed to investigate the problem - feel free to ask me (Program.cs, Startup.cs or maybe some other info).

So, I have ASP .NET Core 5.0 Razor Pages Web Application where I have:

Class for User that extends Microsoft.AspNetCore.Identity.IdentityUser :

 public partial class User: IdentityUser<Int64>
 {
     [Required]
     public string Surname { get; set; }
     [Required]
     public string Name { get; set; }
     [Required]
     public Int64 RoleId { get; set; }
     public virtual Role Role { get; set; }
 }

Class for UserRole that extends Microsoft.AspNetCore.Identity.IdentityRole :

 public class UserRole : IdentityRole<Int64>
 {
     [Required]
     public string Notes { get; set; }
 }

DbContext extends Microsoft.AspNetCore.Identity.EntityFrameworkCore.IdentityDbContext:

 public partial class MyDbContext: IdentityDbContext<User, UserRole, Int64>
 {
     public virtual DbSet<Some_other_class_in_MyDbContext> Some_other_class { get; set; } 

     protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
     { 
             
          ....
             
     }
        
     protected override void OnModelCreating(ModelBuilder modelBuilder)
     {
         //To get rid of an error - "The entity type 'IdentityUserLogin<long>' requires a primary key to be defined"
         base.OnModelCreating(modelBuilder);
            
         ....
            
     }
 }

I've scaffolded default Identity pages (Register, Login, User data edit, etc.) and added some more pages for displaying some user-related diagrams.
After that I simply use:

  1. Microsoft.AspNetCore.Identity.SignInManager for Login, Logout and check whether user is logged-in (PasswordSignInAsync(), SignOutAsync() and IsSignedIn())

  2. Microsoft.AspNetCore.Identity.UserManager to create user, assign user to role and get current user (CreateAsync(), AddToRoleAsync() and GetUserId\GetUserAsync())

Most of that functions use Microsoft.AspNetCore.Mvc.Razor.ClaimsPrincipal User (the comment fo this field is - Gets the System.Security.Claims.ClaimsPrincipal of the current logged in user). And I suppose user identification to work just out-of-the-box.

But while performing some concurrency tests for working with different users that are signed-in at the same time (several browsers on same PC or several PC's connection same web server) - I've faced a user identifying problem. If two users sing-in to different login accounts at almost same time they can both have same value in Microsoft.AspNetCore.Mvc.Razor.ClaimsPrincipal User variable (Identity system recognize them as same user). I definitelly know that because each page in my application has User Name and Surname on the top of it. If second user (who has incorrect value in variable) after that refreshes the page he is currently on - he obtains correct value in variable. Very strange...

My application has over 40 different pages. I can't even imagine that I need to have some collection to persist each page's current state for each logged-in user. For me It's a huge overkill - as I said earlier, I expected Identity to just work out-of-the-box. There are several tables in my database (AspNetRoleClaims, AspNetUserClaims, AspNetUserLogins, AspNetUserClaims) that I've expected to hold all necessary data for this system to work.

So looks like I'm doing something wrong way. Any help is appreciated. Thank you in advance.

UPDATE: @AgaveJoe @Bruce-SqlWork

This symptom could indicate a bug in the application or a bug in the test


Most likely a coding error on your part. Do you store user in a static?

Looks like I've found the source of the problem: Identity system itself is working correctly but I store some data (for example user FullName) in a page model's static variables that I obtain something like that:

 public void OnGet()
 {
      var userId = _userManager.GetUserId(User);
      if (userId != null)
      {
            var user = _context.Users.Include(u => u.Role).Single(u => u.Id == long.Parse(userId));
            //filling static variable
            userFullName = $"{user.Name} {user.Surname}";
            ....
      {
 }

On my page there is a menu (html select item) and when the item is changed I send a POST request to recalculate some data on the page. Page model constructor is called and userFullName becomes null but OnGet is NOT fired afterwards and therefore userFullName is NOT filled. To preserve some data between requests I decided to use static variables. To tell you the truth I was adviced to use static variables in this case on StackOveflow.
Now I see that it was a bad decision for a multi-user application. So my questions are:

  1. How can I persist some data between requests?
    a) As far as I know ViewData, ViewBag, and TempData are destroyed at the end of each request.
    b) sessionStorage is accessible from JavaScript but I prefer pure c# solution.
    c) I cannot use page model constructor because Microsoft.AspNetCore.Mvc.Razor.ClaimsPrincipal User is null there.
    d) I suppose I can perform a check and fill in all the required data directly on the ASP Razor page (inside @{ ... } tag) - but is it a good practice and recommended in production?

  2. What is the best practice to obtain current user:
    a) var userId = _userManager.GetUserId(User); var user = _context.Users.Include(u => u.Role).Single(u => u.Id == long.Parse(userId));
    b) var user = _userManager.GetUserAsync(User).Result;
    c) var user = await _userManager.GetUserAsync(User);

Any help is appreciated. Thank you in advance.






dotnet-csharpdotnet-aspnet-core-razor
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

AgaveJoe avatar image
0 Votes"
AgaveJoe answered bairog commented

The username and claims are cached in an authentication cookie after a successful login. Every request after the login request passes the authentication cookie to the application where the middleware reads the cookie contents and authorizes the request.

If second user (who has incorrect value in variable) after that refreshes the page he is currently on - he obtains correct value in variable. Very strange...

This symptom could indicate a bug in the application or a bug in the test. One possibility is a threading issue due to using a static variable or a singleton to store user information. Keep in mind, this is only a guess. It is tough to provide an accurate code review without the source code and steps to reproduce this issue. However, Identity creates a relationship to roles and Surname and Name should/could be a claim rather than part of the User. Claims further define a user.

I recommend creating a new Visual Studio project using the "Individual Account" option. Execute the migration and create the test accounts. Then run the same test(s). If the results are the same then there is a problem with the test. Otherwise, there is a problem with the code.

· 1
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Looks like I've found the source of the problem - see UPDATE: section in my question.

0 Votes 0 ·
Bruce-SqlWork avatar image
0 Votes"
Bruce-SqlWork answered bairog commented

Most likely a coding error on your part. Do you store user in a static?

· 3
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Looks like I've found the source of the problem - see UPDATE: section in my question.

0 Votes 0 ·

As statics are application shared not request, you can not store user/request specific data.

The docs cover state management pretty well.

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/app-state?view=aspnetcore-5.0

0 Votes 0 ·
bairog avatar image bairog Bruce-SqlWork ·

Thank you, I will read that docs.
And what about my second question (What is the best practice to obtain current user:)?

0 Votes 0 ·
AgaveJoe avatar image
0 Votes"
AgaveJoe answered AgaveJoe commented

How can I persist some data between requests?

Your design stores the name and surname in the database which is always available between requests. Given your updated post. I assume you want to cache the data?

If so, follow my recommendation and store the name and surname in a claim not in the user table. Claims are extra information that further define a user and significant part of Razor Page/MVC security, I might add. Simply write an extension method to read the claims collection and return the name+surname.









· 4
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

Thank you, I will read some docs about claims.
And what about my second question (What is the best practice to obtain current user:)?

0 Votes 0 ·

And what about my second question (What is the best practice to obtain current user:)?

User data is cached in the authentication cookie after a successful login. The cookie middleware exposes common user data through the User object.

 User.Identity.Name

I use the UserManager API when manipulating the User entity or adding a claim/role. Sometimes populating the User entity is overkill and all you need are the User Claims.

 var user = await _userManager.GetUserAsync(User);

Claims are automatically cached in the authentication cookie after a successful login. Simply query the User.Identity.

 var identity = (ClaimsIdentity)User.Identity;
 IEnumerable<Claim> claims = identity.Claims;
 var claim = claims.FirstOrDefault(c => c.Type == "Surname");
 var surname = claim.Value;


I would write an extension method to return the name+surname. Invoke the extension method directly in the markup.


0 Votes 0 ·

Ok, I understand now that claim is suitable for caching some small amount of data (e. g. user Name and Surname).
But what if I want to cache large amount of data. For example on one form I load from database all employee from current user's company: technically it's List<UserInfo> where UserInfo is a class with lots of employee's info (Name, Surname, Date of birth, Start date in company, Current department, Current position, etc.). It is displayed as a table on a page.
Besides there is select element with different filters (Department heads, Non-formal leader, Skilled employee, Newbie, i. e.). User can pick a filter that will be upplied all employee list (and table content changes accordingly).
So my goal is to cache all employee list on page Get and later on any page Post just filter data from that cache (or display full list if no filter is selected).
As you said claims are stored in an authentication cookie - so I suppose they are not designed to store large amounts of data, aren't they?

0 Votes 0 ·

But what if I want to cache large amount of data. For example on one form I load from database all employee from current user's company:

Use standard cache APIs and patterns in .NET 5.

Cache in-memory in ASP.NET Core
Response caching in ASP.NET Core


0 Votes 0 ·