Identity: Get user claims immediately after signin

Created on 30 Nov 2015  路  11Comments  路  Source: aspnet/Identity

I customized the out-of-the-box AccountController so that I add a custom Claim before calling userManager.CreateAsync(), both when registering

  • with a local account (email/password), in the Register() method of the controller
  • with an external provider (google/facebook), in the ExternalLoginConfirmation() of the controller

Now what I'd like to do is, immediately after the user logs in, both

  • in the Login() method after calling signInManager.PasswordSignInAsync() and
  • in the ExternalLoginCallback() method after calling signInManager.ExternalLoginSignInAsync()

...to retrieve that claim, ideally without hitting the DB to get the User.

I noticed that if I look at User directly after sign-in, the Claims collection is empty. However, if I look at it in a subsequent controller action the Claims collection is populated and has my custom claim in it.

The question is, why isn't the Claims populated immediately after sign-in (I guess the sign-in code doesn't refresh the CurrentPrincipal?) and is there another place to check directly after sign-in to get the claims without hitting the DB?

question

Most helpful comment

@HaoK I realize it's an old thread, so if you don't respond, that's fine. Why do you feel it's "a bit weird" to want the ClaimsPrincipal updated after login? I understand from a server perspective there's nothing really to authenticate, but in the context of a SPA, a browser might need information about the claims of the user that just logged in for customizing the UI. Using ASP.NET Identity 1.1.0 with EF I'm finding it surprisingly difficult to get a populated Claims list.

I'm not looking for a solution to my problem here, I'd open a new thread for that; just curious why you think it's a bit weird whereas it seems natural to me.

All 11 comments

You need to refresh the CurrentPrincipal after adding Claims.

Not sure I understand what you mean. I add the Claims when the user registers and they get saved to the DB. Later (meaning maybe much later, e.g. days) the user logs in: at this point I don't have the claims to add, they are already saved in the DB, I just have the user credentials which I use to call the SignInAsync() method.

What I was wondering is if the framework should populate the Claims on the CurrentPrincipal itself so that I don't have to hit the DB again, as this seems to happen automatically on subsequent requests after the user is signed in.

@HaoK ?

Can you please post a sample which reproes this? esp on how are you adding claims, signing in the user and getting the claims for the user.

@rustd Here's a example that worked for me to solve a similar problem.

I'm using the following in the controllers OnActionExecuting override. Request.IsAuthenticated is false before this, and true after.
This excerpt from that function logs in the user after querying the user table for a unique key:

                        var authuser = UserManager.Users.SingleOrDefault(m => m.PersonNo == personno);
                        if (authuser != null)
                        {
                            SignInManager.SignIn(authuser, true, true);

                            var newPrincipal = new UserWrapperPrincipal(HttpContext, authuser, new UserWrapperIdentity(authuser));
                            HttpContext.User = newPrincipal;
                        }

Need these two classes:

    public class UserWrapperIdentity : System.Security.Principal.GenericIdentity
    {
        public UserWrapperIdentity(ApplicationUser authuser) : base(authuser.UserName)
        {
            List<Claim> claims = new List<Claim>{
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name", authuser.UserName),
                new Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier", authuser.Id)
            };
            AddClaims(claims);
        }

    }

    public class UserWrapperPrincipal : System.Security.Principal.IPrincipal
    {
        private readonly ApplicationUser _user;
        private readonly System.Security.Principal.IIdentity _identity;
        private readonly HttpContextBase context;

        public UserWrapperPrincipal(HttpContextBase context, ApplicationUser user, System.Security.Principal.IIdentity identity)
        {
            _user = user;
            _identity = identity;
            this.context = context;
        }

        private IList<string> RoleNames
        {
            get { return context.GetOwinContext().GetUserManager<ApplicationUserManager>().GetRoles(_user.Id).Select(role => role.ToString()).ToList(); }
        }

        public System.Security.Principal.IIdentity Identity { get { return _identity; } }

        public bool IsInRole(string role) { return RoleNames.Contains(role); }

    }

Normally the ClaimsPrincipal is only modified with the result of sign in for the next request when it returns with the Cookie. If you want it to happen immediately (which is a bit weird), then you need to do something like what you are doing now, where you modify the Principal on the context yourself.

@HaoK I realize it's an old thread, so if you don't respond, that's fine. Why do you feel it's "a bit weird" to want the ClaimsPrincipal updated after login? I understand from a server perspective there's nothing really to authenticate, but in the context of a SPA, a browser might need information about the claims of the user that just logged in for customizing the UI. Using ASP.NET Identity 1.1.0 with EF I'm finding it surprisingly difficult to get a populated Claims list.

I'm not looking for a solution to my problem here, I'd open a new thread for that; just curious why you think it's a bit weird whereas it seems natural to me.

The request was either authenticated or not, login will change it so the next request will be authenticated.

Getting the claims for a signed in user is not really identity, that's Authentication mostly. Usually that's just accessing httpContext.User

Thanks for the quick response. I still don't think wanting convenient access to a just-logged-in-user's claims is weird at all. Regarding httpContext.User, what was surprising to me was that User.Claims was empty right after signInManager.PasswordSignIn in both Console.WriteLines below:

[HttpPost, AllowAnonymous]
public async Task<IActionResult> Login([FromBody] LoginViewModel model) {
    var result = await _signInManager.PasswordSignInAsync(model.Username, model.Password, true, false);
    if (result.Succeeded) {
        Console.WriteLine(this.User.Claims); // zero!?
        var user = await _userManager.FindByNameAsync(model.Username);
        Console.WriteLine(user.Claims.Count); // zero!?
    }
    ...
}

Note that if I perform a log in again when I'm already logged in, both Claims collections are populated.

Does this seem like expected behavior for an EF-backed identity setup? If not I can consider opening a new thread.

Jason

You need to mimic what identity does to generate the claims for the user if you want to see them, you can do this via the IUserClaimsPrincipalFactory service that identity uses.

The navigation properties for the user are not populated by default, so I wouldn't rely on user.Claims being consistent as its dependent on what store apis get called by identity. If you want to get the user.Claims, you should call _userManager.GetClaimsAsync(user)

_userManager.GetClaimsAsync(user) -- nice! Wish I'd noticed that when I was wrestling with this issue. Big thanks for your time!!!

Was this page helpful?
0 / 5 - 0 ratings