3

Is there a way to store some custom datas from the user inside the cookie generated by the Identity API ?

We are building an multi-tenant application, and so multiple companies can access to the same instance of our application. That's why I need, for a specific user, to store company code from the user in the identity cookie to retrieve the datas from the user when he returned on the web application after closing the browser.

Christophe Gigax
  • 3,211
  • 4
  • 25
  • 37

1 Answers1

14

you could achieve that by implementing a custom UserClaimsPrincipalFactory and adding a custom claim for your store number, then it would be stored in the cookie with the other claims.

Below is example code from my project where I am adding several custom claims including a SiteGuid because my scenario is also multi-tenant

using cloudscribe.Core.Models;
using Microsoft.AspNet.Identity;
using Microsoft.Extensions.OptionsModel;
using System;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;

namespace cloudscribe.Core.Identity
{
    public class SiteUserClaimsPrincipalFactory<TUser, TRole> : UserClaimsPrincipalFactory<TUser, TRole>
    where TUser : SiteUser
    where TRole : SiteRole
{
    public SiteUserClaimsPrincipalFactory(
        ISiteRepository siteRepository,
        SiteUserManager<TUser> userManager,
        SiteRoleManager<TRole> roleManager, 
        IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
    {
        if (siteRepository == null) { throw new ArgumentNullException(nameof(siteRepository)); }

        siteRepo = siteRepository;
        options = optionsAccessor.Value;
    }

    private ISiteRepository siteRepo;
    private IdentityOptions options;

    public override async Task<ClaimsPrincipal> CreateAsync(TUser user)
    {
        if (user == null)
        {
            throw new ArgumentNullException("user");
        }

        var userId = await UserManager.GetUserIdAsync(user);
        var userName = await UserManager.GetUserNameAsync(user);

        var id = new ClaimsIdentity(
            options.Cookies.ApplicationCookie.AuthenticationScheme,
            Options.ClaimsIdentity.UserNameClaimType,
            Options.ClaimsIdentity.RoleClaimType
            );

            id.AddClaim(new Claim(Options.ClaimsIdentity.UserIdClaimType, userId));
            id.AddClaim(new Claim(Options.ClaimsIdentity.UserNameClaimType, userName));

            if (UserManager.SupportsUserSecurityStamp)
            {
                id.AddClaim(new Claim(Options.ClaimsIdentity.SecurityStampClaimType,
                await UserManager.GetSecurityStampAsync(user)));
            }

            if (UserManager.SupportsUserRole)
            {
                var roles = await UserManager.GetRolesAsync(user);
                foreach (var roleName in roles)
                {
                    id.AddClaim(new Claim(Options.ClaimsIdentity.RoleClaimType, roleName));
                    if (RoleManager.SupportsRoleClaims)
                    {
                        var role = await RoleManager.FindByNameAsync(roleName);
                        if (role != null)
                        {
                            id.AddClaims(await RoleManager.GetClaimsAsync(role));
                        }
                    }
                }
            }
            if (UserManager.SupportsUserClaim)
            {
                id.AddClaims(await UserManager.GetClaimsAsync(user));
            }

            ClaimsPrincipal principal = new ClaimsPrincipal(id);

            if (principal.Identity is ClaimsIdentity)
            {
                ClaimsIdentity identity = (ClaimsIdentity)principal.Identity;

                Claim displayNameClaim = new Claim("DisplayName", user.DisplayName);
                if (!identity.HasClaim(displayNameClaim.Type, displayNameClaim.Value))
                {
                    identity.AddClaim(displayNameClaim);
                }

                Claim emailClaim = new Claim(ClaimTypes.Email, user.Email);
                if (!identity.HasClaim(emailClaim.Type, emailClaim.Value))
                {
                    identity.AddClaim(emailClaim);
                }

                ISiteSettings site = await siteRepo.Fetch(user.SiteId, CancellationToken.None);

                if (site != null)
                {
                    Claim siteGuidClaim = new Claim("SiteGuid", site.SiteGuid.ToString());
                    if (!identity.HasClaim(siteGuidClaim.Type, siteGuidClaim.Value))
                    {
                        identity.AddClaim(siteGuidClaim);
                    }

                }      

            }

            return principal;

        }
    }
}

Then in Startup you need to register your custom factory so it gets injected and used instead of the default one

services.AddScoped<IUserClaimsPrincipalFactory<SiteUser>, SiteUserClaimsPrincipalFactory<SiteUser, SiteRole>>();

another approach is to use Claims Transformation, however this approach does not store the extra claims in a cookie but instead updates the claims on each request, that is it adds more claims to the ones from the cookie for the lifetime of the request but doesn't modify the claims in the cookie.

public class ClaimsTransformer : IClaimsTransformer
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ((ClaimsIdentity)principal.Identity).AddClaim(new Claim("ProjectReader", "true"));
        return Task.FromResult(principal);
    }
}

then in startup.cs:

app.UseClaimsTransformation(new ClaimsTransformationOptions
{
    Transformer = new ClaimsTransformer()
});
Fabien Ménager
  • 140,109
  • 3
  • 41
  • 60
Joe Audette
  • 35,330
  • 11
  • 106
  • 99
  • 1
    Thanks it worked ! I bypassed all stuff about UserClaimsPrincipalFactory because it's too complex for our use, and I simply use HttpContext when I need it to sign in. I added two more Claim and I'm able to recover informations on the second use of my application ! :) – Christophe Gigax Apr 08 '16 at 13:21
  • Joe, using this method, can the cookie be tampered with such as a malicious user editing the "SiteGuid" of their cookie to something else? Granted a valid Guid would be hard to guess, but I'm considering using simple AccountId's (key values in the DB) for filtering user's access to data. Is this safe? – John Yost Apr 26 '16 at 03:50
  • I believe that Microsoft is using dataprotection api to encrypt roles and claims stored in the auth cookie – Joe Audette Apr 26 '16 at 11:26