Search code examples
c#asp.net-coreasp.net-core-mvcasp.net-identity-3identityserver4

Cache user profile with external authentication .NET Core IdentityServer4


New to the whole Identity concept but I've had a couple of Google searches and haven't found a reply I felt fitting.

I'm using .NET Core 1.0.0 with EF Core and IdentityServer 4 (ID4). The ID4 is on a separate server and the only information I get in the client is the claims. I'd like to have access to the full (extended) user profile, preferrably from User.Identity.

So how to I set up so that the User.Identity is populated with all the properties on the ApplicationUser model without sending a DB request every time? I'd like the information to be stored in cache on authentication until the session ends.

What I don't want to do is that in each controller set up a query to get the additional information. All controllers on the client will be inheriting from a base controller, meaning I could DI some service if that's necessary.

Thanks in advance.

Client

app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationScheme = "Cookies"
        });

        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            AuthenticationScheme = "oidc",
            SignInScheme = "Cookies",

            Authority = Configuration.GetSection("IdentityServer").GetValue<string>("Authority"),
            RequireHttpsMetadata = false,
            ClientId = "RateAdminApp"
        });

ID4

app.UseIdentity();

app.UseIdentityServer();

services.AddDeveloperIdentityServer()
            .AddOperationalStore(builder => builder.UseSqlServer("Server=localhost;Database=Identities;MultipleActiveResultSets=true;Integrated Security=true", options => options.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)))
            .AddConfigurationStore(builder => builder.UseSqlServer("Server=localhost;Database=Identities;MultipleActiveResultSets=true;Integrated Security=true", options => options.MigrationsAssembly(typeof(Startup).GetTypeInfo().Assembly.GetName().Name)))
            .AddAspNetIdentity<ApplicationUser>();

ApplicationUser Model

public class ApplicationUser : IdentityUser
{
    [Column(TypeName = "varchar(100)")]
    public string FirstName { get; set; }
    [Column(TypeName = "varchar(100)")]
    public string LastName { get; set; }
    [Column(TypeName = "nvarchar(max)")]
    public string ProfilePictureBase64 { get; set; }
}

Solution

  • If you want to transform claims on the identity server, for your case(you use aspnet identity) overriding UserClaimsPrincipalFactory is a solution(see Store data in cookie with asp.net core identity).

    public class AppClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>
    {
        public AppClaimsPrincipalFactory(
            UserManager<ApplicationUser> userManager,
            RoleManager<IdentityRole> roleManager,
            IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor)
        {
        }
    
        public async override Task<ClaimsPrincipal> CreateAsync(ApplicationUser user)
        {
            var principal = await base.CreateAsync(user);
    
            ((ClaimsIdentity)principal.Identity).AddClaims(new[] {
                 new Claim("FirstName", user.FirstName)
            });
    
            return principal;
        }
    }
    
    // register it
    services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, AppClaimsPrincipalFactory>();
    

    Also you can use events(on the client application) to add extra claims into cookie, it provides claims until the user log out.

    There are two(maybe more than) options:

    First using OnTicketReceived of openidconnect authentication:

        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            AuthenticationScheme = "oidc",
            SignInScheme = "Cookies",
    
            Authority = Configuration.GetSection("IdentityServer").GetValue<string>("Authority"),
            RequireHttpsMetadata = false,
            ClientId = "RateAdminApp",
            Events = new OpenIdConnectEvents
            {
               OnTicketReceived = e =>
               {
                   // get claims from user profile
                   // add claims into e.Ticket.Principal
                   e.Ticket = new AuthenticationTicket(e.Ticket.Principal, e.Ticket.Properties, e.Ticket.AuthenticationScheme);
    
                   return Task.CompletedTask;
                }
            }
        });
    

    Or using OnSigningIn event of cookie authentication

    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
         AuthenticationScheme = "Cookies",
         Events = new CookieAuthenticationEvents()
         {
             OnSigningIn = async (context) =>
             {
                 ClaimsIdentity identity = (ClaimsIdentity)context.Principal.Identity;
                 // get claims from user profile
                 // add these claims into identity
             }
         }
    });
    

    See similar question for solution on the client application: Transforming Open Id Connect claims in ASP.Net Core