0

I have implemented Identity Server for Auth Code Flow.

What is correct way to persist the claims (in OnTicketReceived or OnTicketValidated as shown below), so that in subsequent calls to Blazor pages, I could receive User aka ClaimPrincipal populated for my use?

Here is code of middleware of my resource server:

    public void ConfigureServices(IServiceCollection services)
    {
          //....

        services.AddIdentity<ApplicationUser, IdentityRole>(options =>
          {
              options.SignIn.RequireConfirmedAccount = false;
              options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10);

              options.SignIn.RequireConfirmedEmail = false;
          })
            .AddRoles<IdentityRole>()
            .AddEntityFrameworkStores<SomeContext>()
            .AddDefaultTokenProviders();

Middleware integration:

       services.AddAuthentication(options =>
        {
            options.DefaultScheme = "cookie";
            options.DefaultChallengeScheme = "oidc";
            options.DefaultSignOutScheme = "oidc";
        })
            .AddCookie("cookie", options =>
            {
                options.Cookie.Name = "__Host-bff";
                options.Cookie.SameSite = SameSiteMode.Strict;
            })
            .AddOpenIdConnect("oidc", options =>
            {
                options.Authority = "https://localhost:5001";
                options.ClientId = "mvc.code";
                options.ClientSecret = "secret";
                options.ResponseType = "code";
                options.ResponseMode = "query";

                options.GetClaimsFromUserInfoEndpoint = true;
                options.MapInboundClaims = false;
                options.SaveTokens = true;

                options.Scope.Clear();
                options.Scope.Add("openid");
                options.Scope.Add("profile");
                

                //Critical Parts
                options.TokenValidationParameters = new()
                {
                    NameClaimType = "name",
                    RoleClaimType = "role"
                };

Authenticated callback from identity server is as following:

                options.Events.OnTicketReceived = async n =>
                {
                    var serviceProvider = n.HttpContext.RequestServices;
                    var accountService = serviceProvider.GetService<IAccountService>() ?? throw new ArgumentNullException("serviceProvider.GetService<IAccountService>()");

                    

I tried using BlazoredSessionStorage etc. but it seems too early to invoke that. We have to wait until OnPrerender or OnInit

I also tried CustomTokenStore. But how does the claim from cookie come back to server?

           var svc = n.HttpContext.RequestServices.GetRequiredService<IUserAccessTokenStore>();
                  
                    if (n.Principal != null)
                    {
                        var userName = n.Principal.FindFirst(x => x.Type == "name")?.Value;


                        await accountService.UserCreateAsync(new NewAccount
                        {
                            Username = userName,
                            FirstName = userFirstName,
                            LastName = userLastName,
                            //ContactId = 100,
                            TenantId = 1
                        });
                        await (authProvider as SomeAuthenticationStateProvider).LoginAsync(new AuthenticationLogin { Username = userName }, 24 * 60);
                    }
                };

 public class CustomTokenStore : IUserAccessTokenStore
{
    ConcurrentDictionary<string, UserAccessToken> _tokens = new ConcurrentDictionary<string, UserAccessToken>();

    public Task ClearTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
    {
        var sub = user.FindFirst("sub").Value;
        _tokens.TryRemove(sub, out _);
        return Task.CompletedTask;
    }

    public Task<UserAccessToken> GetTokenAsync(ClaimsPrincipal user, UserAccessTokenParameters parameters = null)
    {
        var sub = user.FindFirst("sub").Value;
        _tokens.TryGetValue(sub, out var value);
        return Task.FromResult(value);
    }

    public Task StoreTokenAsync(ClaimsPrincipal user, string accessToken, DateTimeOffset expiration, string refreshToken = null, UserAccessTokenParameters parameters = null)
    {
        var sub = user.FindFirst("sub").Value;
        var token = new UserAccessToken
        {
            AccessToken = accessToken,
            Expiration = expiration,
            RefreshToken = refreshToken
        };
        _tokens[sub] = token;
        return Task.CompletedTask;
    }
}
Abhijeet
  • 13,562
  • 26
  • 94
  • 175

2 Answers2

0
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
                .AddAzureAD(options => Configuration.Bind("AzureAd", options));

            services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
            {

                options.ClaimActions.Add(new CustomClaimsFactory(
                                        "userName",
                                        "XXXXX@outlook.com"
                                    ));
                options.TokenValidationParameters = new TokenValidationParameters
                {
                    // Instead of using the default validation (validating against a single issuer value, as we do in
                    // line of business apps), we inject our own multitenant validation logic
                    ValidateIssuer = false,

                    // If the app is meant to be accessed by entire organizations, add your issuer validation logic here.
                    //IssuerValidator = (issuer, securityToken, validationParameters) => {
                    //    if (myIssuerValidationLogic(issuer)) return issuer;
                    //}
                };

                options.Events = new OpenIdConnectEvents
                {
                    OnTicketReceived = context =>
                    {
                        // If your authentication logic is based on users then add your logic here
                        return Task.CompletedTask;
                    },
                    OnAuthenticationFailed = context =>
                    {
                        context.Response.Redirect("/Error");
                        context.HandleResponse(); // Suppress the exception
                        return Task.CompletedTask;
                    },
                    // If your application needs to do authenticate single users, add your user validation below.
                    //OnTokenValidated = context =>
                    //{

                    //     var claims = new List<Claim>
                    //     {
                    //        new Claim(ClaimTypes.Role, "superadmin")
                    //     };
                    //    var appIdentity = new ClaimsIdentity(claims);
                    //    context.Principal.AddIdentity(appIdentity);
                    //    return Task.CompletedTask;
                    //    //return myUserValidationLogic(context.Ticket.Principal);
                    //}
                };
            });

While CustomActionFactory class is defined below

public class CustomClaimsFactory : ClaimAction
    {
        string _ClaimType;
        string _ValueType;
        public CustomClaimsFactory(string claimType, string valueType) : base(claimType, valueType)
        {
            _ClaimType = claimType;
            _ValueType = valueType;
        }
        public override void Run(JObject userData, ClaimsIdentity identity, string issuer)
        {
            identity.AddClaim(new Claim(_ClaimType, _ValueType, issuer));

        }
    }```
Sarang Kulkarni
  • 367
  • 2
  • 6
-1

OnTokenValidated has a method to add the new identity you create with the claim:

OnTokenValidated = ctx =>{ ctx.Principal.AddIdentity(myNewClaim); }
GH DevOps
  • 305
  • 1
  • 11