Search code examples
asp.net-coreasp.net-identity-3

User.Claims.FirstOrDefault().Value is null at Login action (ASP.NET Identity 3)?


I am trying to get the "id" value from User object at Login action after creating a default ASP.NET Core with "Individual User Account" and it is always returns null. the line is (var objUser = User.Claims.FirstOrDefault().Value;)

[HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
        ViewData["ReturnUrl"] = returnUrl;
        if (ModelState.IsValid)
        {
            // This doesn't count login failures towards account lockout
            // To enable password failures to trigger account lockout, set lockoutOnFailure: true
            var result = await _signInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, lockoutOnFailure: false);


            if (result.Succeeded)
            {

                var objUser = User.Claims.FirstOrDefault().Value;

                _logger.LogInformation(1, "User logged in.");
                return RedirectToLocal(returnUrl);
            }
            if (result.RequiresTwoFactor)
            {
                return RedirectToAction(nameof(SendCode), new { ReturnUrl = returnUrl, RememberMe = model.RememberMe });
            }
            if (result.IsLockedOut)
            {
                _logger.LogWarning(2, "User account locked out.");
                return View("Lockout");
            }
            else
            {
                ModelState.AddModelError(string.Empty, "Invalid login attempt.");
                return View(model);
            }
        }

        // If we got this far, something failed, redisplay form
        return View(model);
    }

User object is not null when accessing it from other Action like Home/Index

public IActionResult Index()
    {
        var x = User.Claims.FirstOrDefault().Value;
        return View();
    }

Is there a way to have User object at Login Action after result.Succeeded?

Thanks, AG


Solution

  • I think the issue here is that at the time the user signs in the claimsprincipal is created and the cookie authentication middleware serializes it into the authentication cookie which is added to the Response so it gets sent to the browser as part of the response.

    Due to the mechanics of cookies you can't read the cookie until the next request when the browser passes it back to the server. This is because you set cookies on the Response which passes cookies back to the web browser and you read cookies on the Request which is when the web browser passes cookies to the server. At the time when the post is made to sign in the browser has not passed the authentication cookie because it has not received it yet. You've set it on the response but as mentioned to access it from server code it comes from the request not the response.

    On the next request the cookie is passed as a header and the cookie authentication middleware can then deserialize it from the cookie back into a ClaimsPrincipal which is what "User" is in the code of your controller. So the populated User and Claims is not available until the next request where you can then get it from the Request not the Response.

    So for example if you needed to choose where to redirect based on the claims assigned during login, one way you could do it is to redirect to another action and that action since it is a new request would have access to the claimsprincipal that is deserialized from the cookie and could then redirect again based on the claims. The downside would be the double redirect.

    You could also lookup the user as you have shown in your own answer if that gives you what you need, but there you are getting the ApplicationUser or whatever class represents your user which is not the same thing as a ClaimsPrincipal.