AspNet Identity Core - Custom Claims on Login











up vote
2
down vote

favorite












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


























    up vote
    2
    down vote

    favorite












    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
























      up vote
      2
      down vote

      favorite









      up vote
      2
      down vote

      favorite











      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 at 17:02









      SkelDave

      6861619




      6861619
























          1 Answer
          1






          active

          oldest

          votes

















          up vote
          3
          down vote



          accepted










          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 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',
          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








          up vote
          3
          down vote



          accepted










          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 at 9:42















          up vote
          3
          down vote



          accepted










          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 at 9:42













          up vote
          3
          down vote



          accepted







          up vote
          3
          down vote



          accepted






          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 at 17:30









          Joe Audette

          16k55775




          16k55775












          • 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 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 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 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 at 9:42


















           

          draft saved


          draft discarded



















































           


          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

          404 Error Contact Form 7 ajax form submitting

          How to know if a Active Directory user can login interactively

          TypeError: fit_transform() missing 1 required positional argument: 'X'