0
votes

Introduction

I've been working for the last couple of days on small pet-project with a goal of learning .NET Core 2.0 with Identity backed by Entity Framework Core. It is a typical "WebAPI" type project with cookie based authentication and claims based authorization. It is utilized by some client application (SPA).

Code

Authorization & Authentication flow is configured this way in Startup.cs

services
    .AddIdentity<ApplicationUser, IdentityRole> ()
    .AddEntityFrameworkStores<ApplicationDbContext> ()
    .AddDefaultTokenProviders ();

services
    .AddAuthentication (sharedOptions => {
        sharedOptions.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        sharedOptions.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie ();

My login controller action looks like this:

[HttpPost]
[Route ("login")]
public async Task<IActionResult> Login ([FromBody] LogInCredentialsModel credentials) {
    // Get User for given UserName
    var user = await userManager.Users.FirstOrDefaultAsync (p => p.UserName == credentials.UserName);

    //User not found
    if (user == default (ApplicationUser))
        return StatusCode (400);

    // Check if password is correct
    var result = await signInManager.PasswordSignInAsync (user, credentials.Password, true, false);

    if (result.Succeeded) {
        //Basic claims with Name and Email
        List<Claim> claims = new List<Claim> {
            new Claim (ClaimTypes.Name, user.UserName),
            new Claim (ClaimTypes.Email, user.Email)
        };

        var userRoles = await this.GetUserRoles (user); // Custom helper method to get list of user roles

        // Add Role claims
        foreach (var role in userRoles) {
            claims.Add (new Claim (ClaimTypes.Role, role));
        }

        ClaimsIdentity identity = new ClaimsIdentity (claims, CookieAuthenticationDefaults.AuthenticationScheme);
        ClaimsPrincipal principal = new ClaimsPrincipal (identity);

        // Sign in using cookie scheme
        await HttpContext.SignInAsync (CookieAuthenticationDefaults.AuthenticationScheme, principal, new AuthenticationProperties {
            IsPersistent = true,
        });

        return Ok ();
    } else {
        return StatusCode (400);
    }
}

Problems

  1. These claims will be stored in encrypted user cookie. This means, that if I remove some claim from user and he does not re-log, he will still have old claims assigned. How do I prevent that? Or did I misunderstand the design?
  2. User passes UserName and Password to login route which is then used to sign him in. In my code, I have to first find user with given UserName (1st db hit), then try to sigin in with password using SignInManager (2nd db hit), read roles (3rd db hit) to build ClaimsPrincipal and then use HttpContext.SignInAsync so user cookie with correct claims is created. I personally feel like I'm missing something and in the result my code is overcomplicated, also atleast one of the database queries could be saved here. How to improve this part?
1
So which question are you asking... 1. or 2.? One question per posted question please.spender
These are two aspects of the same mechanism that's why they are included in single question. Quick look at meta.stackoverflow.com makes me believe that this is allowed.wirher
@wirher Why are you building ClaimsPrincipal by hand this way? that should be handled for you by the framework.trailmax
@trailmax Thank for your input. I did this to make my trail-and-error code work. Why didn't framework handle it in my instance? See my comment under Chris Pratt's answer below.wirher

1 Answers

1
votes

The answers to both of your questions are pretty basic, so maybe you should spend some more time with the docs to get a better handle on this. That said:

  1. Yes. You are correct. When you change a claim, you should sign the user out as well. Then, you can choose to automatically sign them in again, without user intervention, or prompt the user to re-login (depending on your personal security preferences).

  2. Why are you doing all this manually? All you need is:

    var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);
    

    That automatically hashes the password, attempts to retrieve the user with that username (email address) and hashed password, and then creates a ClaimsPrincipal with all that information, if successful. One and done.