AspNet Identity Core - Custom Claims on Login












2














I'm trying to extend my identity user by adding a logical 'deleted' column in the database.



I then want to use this value to add a claim to user using a custom UserClaimsPrincipalFactory.



I want to check the 'Deleted' claim on login and reject the user if their account has been deleted.



The problem: When I try and access the claims through User.Claims the user has no claims.



The only was I can make it work is by overwriting the httpcontext user



public class ApplicationClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
{
private readonly IHttpContextAccessor _httpContext;

public ApplicationClaimsIdentityFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, IHttpContextAccessor httpContext) : base(userManager, roleManager, options)
{
_httpContext = httpContext;
}

public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
{
ClaimsPrincipal principal = await base.CreateAsync(user);

ClaimsIdentity claimsIdentity = (ClaimsIdentity) principal.Identity;

claimsIdentity.AddClaim(new Claim("Deleted", user.Deleted.ToString().ToLower()));

//I DON'T WANT TO HAVE TO DO THIS
_httpContext.HttpContext.User = principal;


return principal;
}
}


Login Action:



public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
{
ViewData["ReturnUrl"] = returnUrl;
if (ModelState.IsValid)
{

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

if (result.Succeeded)
{
//No claims exists at this point unless I force the HttpContext user (See above)
if (User.Claims.First(x => x.Type == "Deleted").Value.Equals("true", StringComparison.CurrentCultureIgnoreCase);)
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
await _signInManager.SignOutAsync();
return View(model);
}

.... Continue login code...


My ApplicationUser class



public class ApplicationUser : IdentityUser
{
public bool Deleted { get; set; }
}


And finally my startup registration



services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<DbContext>()
.AddClaimsPrincipalFactory<ApplicationClaimsIdentityFactory>()
.AddDefaultTokenProviders();


Thanks










share|improve this question



























    2














    I'm trying to extend my identity user by adding a logical 'deleted' column in the database.



    I then want to use this value to add a claim to user using a custom UserClaimsPrincipalFactory.



    I want to check the 'Deleted' claim on login and reject the user if their account has been deleted.



    The problem: When I try and access the claims through User.Claims the user has no claims.



    The only was I can make it work is by overwriting the httpcontext user



    public class ApplicationClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
    {
    private readonly IHttpContextAccessor _httpContext;

    public ApplicationClaimsIdentityFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, IHttpContextAccessor httpContext) : base(userManager, roleManager, options)
    {
    _httpContext = httpContext;
    }

    public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
    {
    ClaimsPrincipal principal = await base.CreateAsync(user);

    ClaimsIdentity claimsIdentity = (ClaimsIdentity) principal.Identity;

    claimsIdentity.AddClaim(new Claim("Deleted", user.Deleted.ToString().ToLower()));

    //I DON'T WANT TO HAVE TO DO THIS
    _httpContext.HttpContext.User = principal;


    return principal;
    }
    }


    Login Action:



    public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
    {
    ViewData["ReturnUrl"] = returnUrl;
    if (ModelState.IsValid)
    {

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

    if (result.Succeeded)
    {
    //No claims exists at this point unless I force the HttpContext user (See above)
    if (User.Claims.First(x => x.Type == "Deleted").Value.Equals("true", StringComparison.CurrentCultureIgnoreCase);)
    {
    ModelState.AddModelError(string.Empty, "Invalid login attempt.");
    await _signInManager.SignOutAsync();
    return View(model);
    }

    .... Continue login code...


    My ApplicationUser class



    public class ApplicationUser : IdentityUser
    {
    public bool Deleted { get; set; }
    }


    And finally my startup registration



    services.AddIdentity<ApplicationUser, IdentityRole>()
    .AddEntityFrameworkStores<DbContext>()
    .AddClaimsPrincipalFactory<ApplicationClaimsIdentityFactory>()
    .AddDefaultTokenProviders();


    Thanks










    share|improve this question

























      2












      2








      2







      I'm trying to extend my identity user by adding a logical 'deleted' column in the database.



      I then want to use this value to add a claim to user using a custom UserClaimsPrincipalFactory.



      I want to check the 'Deleted' claim on login and reject the user if their account has been deleted.



      The problem: When I try and access the claims through User.Claims the user has no claims.



      The only was I can make it work is by overwriting the httpcontext user



      public class ApplicationClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
      {
      private readonly IHttpContextAccessor _httpContext;

      public ApplicationClaimsIdentityFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, IHttpContextAccessor httpContext) : base(userManager, roleManager, options)
      {
      _httpContext = httpContext;
      }

      public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
      {
      ClaimsPrincipal principal = await base.CreateAsync(user);

      ClaimsIdentity claimsIdentity = (ClaimsIdentity) principal.Identity;

      claimsIdentity.AddClaim(new Claim("Deleted", user.Deleted.ToString().ToLower()));

      //I DON'T WANT TO HAVE TO DO THIS
      _httpContext.HttpContext.User = principal;


      return principal;
      }
      }


      Login Action:



      public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
      {
      ViewData["ReturnUrl"] = returnUrl;
      if (ModelState.IsValid)
      {

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

      if (result.Succeeded)
      {
      //No claims exists at this point unless I force the HttpContext user (See above)
      if (User.Claims.First(x => x.Type == "Deleted").Value.Equals("true", StringComparison.CurrentCultureIgnoreCase);)
      {
      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
      await _signInManager.SignOutAsync();
      return View(model);
      }

      .... Continue login code...


      My ApplicationUser class



      public class ApplicationUser : IdentityUser
      {
      public bool Deleted { get; set; }
      }


      And finally my startup registration



      services.AddIdentity<ApplicationUser, IdentityRole>()
      .AddEntityFrameworkStores<DbContext>()
      .AddClaimsPrincipalFactory<ApplicationClaimsIdentityFactory>()
      .AddDefaultTokenProviders();


      Thanks










      share|improve this question













      I'm trying to extend my identity user by adding a logical 'deleted' column in the database.



      I then want to use this value to add a claim to user using a custom UserClaimsPrincipalFactory.



      I want to check the 'Deleted' claim on login and reject the user if their account has been deleted.



      The problem: When I try and access the claims through User.Claims the user has no claims.



      The only was I can make it work is by overwriting the httpcontext user



      public class ApplicationClaimsIdentityFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
      {
      private readonly IHttpContextAccessor _httpContext;

      public ApplicationClaimsIdentityFactory(UserManager<ApplicationUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, IHttpContextAccessor httpContext) : base(userManager, roleManager, options)
      {
      _httpContext = httpContext;
      }

      public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
      {
      ClaimsPrincipal principal = await base.CreateAsync(user);

      ClaimsIdentity claimsIdentity = (ClaimsIdentity) principal.Identity;

      claimsIdentity.AddClaim(new Claim("Deleted", user.Deleted.ToString().ToLower()));

      //I DON'T WANT TO HAVE TO DO THIS
      _httpContext.HttpContext.User = principal;


      return principal;
      }
      }


      Login Action:



      public async Task<IActionResult> Login(LoginViewModel model, string returnUrl = null)
      {
      ViewData["ReturnUrl"] = returnUrl;
      if (ModelState.IsValid)
      {

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

      if (result.Succeeded)
      {
      //No claims exists at this point unless I force the HttpContext user (See above)
      if (User.Claims.First(x => x.Type == "Deleted").Value.Equals("true", StringComparison.CurrentCultureIgnoreCase);)
      {
      ModelState.AddModelError(string.Empty, "Invalid login attempt.");
      await _signInManager.SignOutAsync();
      return View(model);
      }

      .... Continue login code...


      My ApplicationUser class



      public class ApplicationUser : IdentityUser
      {
      public bool Deleted { get; set; }
      }


      And finally my startup registration



      services.AddIdentity<ApplicationUser, IdentityRole>()
      .AddEntityFrameworkStores<DbContext>()
      .AddClaimsPrincipalFactory<ApplicationClaimsIdentityFactory>()
      .AddDefaultTokenProviders();


      Thanks







      c# asp.net-core identity claims-based-identity






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 19 '18 at 17:02









      SkelDave

      6911619




      6911619
























          1 Answer
          1






          active

          oldest

          votes


















          3














          I think the problem is you are trying to do it all in one request when the login action is posted.



          The User you have in that method is a claimsprincipal but is not authenticated, it was deserialized from the request by the auth middleware before the code to SignIn was invoked and before your claimsprincipal factory method was called.



          The signin method did create the new authenticated claimsprincipal and should have serialized it into the auth cookie, so on the next request the User would be deserialized from the cookie and would be authenticated, but that deserialization already happened for the current request. So the only way to change it for the current request is to reset the User on the current httpcontext as you have found.



          I think it would be better to reject the user a different way not after login success, it would be better to check that at a lower level and make login fail, rather than succeed and then signout. I did this in my project in a custom userstore.






          share|improve this answer





















          • Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
            – SkelDave
            Nov 20 '18 at 9:42











          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53379463%2faspnet-identity-core-custom-claims-on-login%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          3














          I think the problem is you are trying to do it all in one request when the login action is posted.



          The User you have in that method is a claimsprincipal but is not authenticated, it was deserialized from the request by the auth middleware before the code to SignIn was invoked and before your claimsprincipal factory method was called.



          The signin method did create the new authenticated claimsprincipal and should have serialized it into the auth cookie, so on the next request the User would be deserialized from the cookie and would be authenticated, but that deserialization already happened for the current request. So the only way to change it for the current request is to reset the User on the current httpcontext as you have found.



          I think it would be better to reject the user a different way not after login success, it would be better to check that at a lower level and make login fail, rather than succeed and then signout. I did this in my project in a custom userstore.






          share|improve this answer





















          • Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
            – SkelDave
            Nov 20 '18 at 9:42
















          3














          I think the problem is you are trying to do it all in one request when the login action is posted.



          The User you have in that method is a claimsprincipal but is not authenticated, it was deserialized from the request by the auth middleware before the code to SignIn was invoked and before your claimsprincipal factory method was called.



          The signin method did create the new authenticated claimsprincipal and should have serialized it into the auth cookie, so on the next request the User would be deserialized from the cookie and would be authenticated, but that deserialization already happened for the current request. So the only way to change it for the current request is to reset the User on the current httpcontext as you have found.



          I think it would be better to reject the user a different way not after login success, it would be better to check that at a lower level and make login fail, rather than succeed and then signout. I did this in my project in a custom userstore.






          share|improve this answer





















          • Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
            – SkelDave
            Nov 20 '18 at 9:42














          3












          3








          3






          I think the problem is you are trying to do it all in one request when the login action is posted.



          The User you have in that method is a claimsprincipal but is not authenticated, it was deserialized from the request by the auth middleware before the code to SignIn was invoked and before your claimsprincipal factory method was called.



          The signin method did create the new authenticated claimsprincipal and should have serialized it into the auth cookie, so on the next request the User would be deserialized from the cookie and would be authenticated, but that deserialization already happened for the current request. So the only way to change it for the current request is to reset the User on the current httpcontext as you have found.



          I think it would be better to reject the user a different way not after login success, it would be better to check that at a lower level and make login fail, rather than succeed and then signout. I did this in my project in a custom userstore.






          share|improve this answer












          I think the problem is you are trying to do it all in one request when the login action is posted.



          The User you have in that method is a claimsprincipal but is not authenticated, it was deserialized from the request by the auth middleware before the code to SignIn was invoked and before your claimsprincipal factory method was called.



          The signin method did create the new authenticated claimsprincipal and should have serialized it into the auth cookie, so on the next request the User would be deserialized from the cookie and would be authenticated, but that deserialization already happened for the current request. So the only way to change it for the current request is to reset the User on the current httpcontext as you have found.



          I think it would be better to reject the user a different way not after login success, it would be better to check that at a lower level and make login fail, rather than succeed and then signout. I did this in my project in a custom userstore.







          share|improve this answer












          share|improve this answer



          share|improve this answer










          answered Nov 19 '18 at 17:30









          Joe Audette

          16.7k55975




          16.7k55975












          • Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
            – SkelDave
            Nov 20 '18 at 9:42


















          • Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
            – SkelDave
            Nov 20 '18 at 9:42
















          Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
          – SkelDave
          Nov 20 '18 at 9:42




          Thanks - this really helped show where I was going wrong. I've implemented my own SignInManager CanSignInAsync method and handled it at that level.
          – SkelDave
          Nov 20 '18 at 9:42


















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.





          Some of your past answers have not been well-received, and you're in danger of being blocked from answering.


          Please pay close attention to the following guidance:


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53379463%2faspnet-identity-core-custom-claims-on-login%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          Can a sorcerer learn a 5th-level spell early by creating spell slots using the Font of Magic feature?

          ts Property 'filter' does not exist on type '{}'

          mat-slide-toggle shouldn't change it's state when I click cancel in confirmation window