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

Alexey Leonovich 96 Reputation points
2021-09-22T08:42:32.82+00:00

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.com)

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.
ASP.NET Core
ASP.NET Core
A set of technologies in the .NET Framework for building web applications and XML web services.
4,157 questions
C#
C#
An object-oriented and type-safe programming language that has its roots in the C family of languages and includes support for component-oriented programming.
10,234 questions
0 comments No comments
{count} votes

3 answers

Sort by: Most helpful
  1. AgaveJoe 26,191 Reputation points
    2021-09-22T11:09:35.737+00:00

    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.


  2. Bruce (SqlWork.com) 55,601 Reputation points
    2021-09-22T14:43:11.487+00:00

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


  3. AgaveJoe 26,191 Reputation points
    2021-09-24T11:24:23.247+00:00

    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.