Proper place for adding custom user claims

3.9k views Asked by At

There is a user class, which has field named "Avatar", which stores path to his profile picture. I want to show it in header inside the partial view. So I desided to add a claim to user identity. I put this lines of code inside my IdentityConfig.cs class:

 public override Task<ClaimsIdentity> CreateUserIdentityAsync(AppUser user)
        {
            if(!System.String.IsNullOrEmpty(user.Avatar))
                user.Claims.Add(new AppUserClaim() { ClaimType = "avatar", ClaimValue = user.Avatar});

            return user.GenerateUserIdentityAsync((AppUserManager)UserManager);
        }

But there is a problem: after a period of time (aprox. 1 hour) this Claim disappears and there's no avatar shown. I found out, that asp.net identity framework regenerates user's identity every 30 minutes (default). And according to this:

 regenerateIdentityCallback: (manager, user) =>
                            user.GenerateUserIdentityAsync(manager)

it calls GenerateUserIdentityAsyncmethod of user's class. At this momment it becomes not clear to me. There are two, on the first sight, similar methods of generating user identity:

  1. Inside AppUser class, which takes usermanager class as a parameter - public async Task<ClaimsIdentity> GenerateUserIdentityAsync(AppUserManager manager)
  2. Inside SignInManager- public override Task<ClaimsIdentity> CreateUserIdentityAsync(AppUser user)

What's the purpose of that? Where each of this methods has been used? And which one should I use to add custom user claim?

2

There are 2 answers

0
Martin On BEST ANSWER

I've refactored a standard ASP.NET MVC project a little so I don't repeat the code for adding claims.

Startup.Auth.cs:

public void ConfigureAuth(IAppBuilder app, Container container)
{
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        Provider = new CookieAuthenticationProvider
        {
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, User>(
                validateInterval: TimeSpan.FromMinutes(30),
                regenerateIdentity: (manager, user) => IdentityHelper.GenerateUserIdentityAsync(user, manager))
        }
    });
}

Then I made a static helper method to generate the identity:

public async Task<ClaimsIdentity> GenerateUserIdentityAsync(User user, UserManager<User> manager)
{
    var userIdentity = await manager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie).ConfigureAwait(false);

    userIdentity.AddClaim(new Claim("Key", "Value"));

    return userIdentity;
}

Now you will be able to reuse this helper from your SignInManager.

public class ApplicationSignInManager : SignInManager<User, string>
{
    public ApplicationSignInManager(ApplicationUserManager userManager, IAuthenticationManager authenticationManager)
        : base(userManager, authenticationManager)
    {
    }

    public override Task<ClaimsIdentity> CreateUserIdentityAsync(User user)
    {
        return IdentityHelper.GenerateUserIdentityHelperAsync(user, (ApplicationUserManager)UserManager);
    }
}
0
Sandro Stadler On

There is another way. For .NET 6.

using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;

public class MyClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
        var claimType = "myNewClaim";
        if (!principal.HasClaim(claim => claim.Type == claimType))
        {
            claimsIdentity.AddClaim(new Claim(claimType, "myClaimValue"));
        }

        principal.AddIdentity(claimsIdentity);
        return Task.FromResult(principal);
    }
}

The IClaimsTransformation interface and the MyClaimsTransformation class can be registered as a service:

builder.Services.AddTransient<IClaimsTransformation, MyClaimsTransformation>();

Source: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/claims?view=aspnetcore-6.0