Base solution for your next web application
Open Closed

External Authorization with OpenIdConnect - Roles from UserInfoEndpoint #9964


User avatar
0
henryand created

I'm working with the ANZ version 10.1 Angulare with Core/dotNet5 connecting to an external authentication server using OpenIdConnect.
They have an authentication endpoint and a userinfo endpoint.

I added an entry for "UserInfoEndpoint" in the"OpenId" section of AppSettings.json I updated getOpenIdConnectConfig with: authConfig.userinfoEndpoint = loginProvider.additionalParams['UserInfoEndpoint']; authConfig.responseType = 'id_token token'; authConfig.scope = 'openid profile email';

unfortunately, externalAuthManager is not visible and ExternalAuthUserInfo is not modifiable.

So, my question is whether there is any way to map the access token and id token results to get more than just first name, last name, and email? The customer's auth server is theoretically sending us the first name, last name, and email plus username and authorization data including user roles.

thank you Rocco


4 Answer(s)
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi @henryand

    At the moment, it is not possible to achive this but we can share related class with you. Just, copy it to your project and use it TokenAuthController. We will add such functionality for the following versions.

    public class OpenIdConnectAuthProviderApi : ExternalAuthProviderApiBase
        {
            public const string Name = "OpenIdConnect";
    
            public async override Task<ExternalAuthUserInfo> GetUserInfo(string token)
            {
                var issuer = ProviderInfo.AdditionalParams["Authority"];
                if (string.IsNullOrEmpty(issuer))
                {
                    throw new ApplicationException("Authentication:OpenId:Issuer configuration is required.");
                }
    
                var configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
                    issuer + "/.well-known/openid-configuration",
                    new OpenIdConnectConfigurationRetriever(),
                    new HttpDocumentRetriever());
    
                var validatedTokenResult = await ValidateToken(token, issuer, configurationManager);
    
                var fullNameClaim = validatedTokenResult.Principal.Claims.FirstOrDefault(c => c.Type == "name");
    
                if (fullNameClaim == null)
                {
                    throw new UserFriendlyException("name claim is missing !");
                }
    
                var emailClaim = validatedTokenResult.Principal.Claims.FirstOrDefault(c => c.Type == "unique_name");
    
                if (emailClaim == null)
                {
                    throw new UserFriendlyException("unique_name claim is missing !");
                }
    
                var fullNameParts = fullNameClaim.Value.Split(' ');
    
                return new ExternalAuthUserInfo
                {
                    Provider = Name,
                    ProviderKey = validatedTokenResult.Token.Subject,
                    Name = fullNameParts[0],
                    Surname = fullNameParts.Length > 1 ? fullNameParts[1] : fullNameParts[0],
                    EmailAddress = emailClaim.Value
                };
            }
    
            private async Task<ValidateTokenResult> ValidateToken(
                string token,
                string issuer,
                IConfigurationManager<OpenIdConnectConfiguration> configurationManager,
                CancellationToken ct = default(CancellationToken))
            {
                if (string.IsNullOrEmpty(token))
                {
                    throw new ArgumentNullException(nameof(token));
                }
    
                if (string.IsNullOrEmpty(issuer))
                {
                    throw new ArgumentNullException(nameof(issuer));
                }
    
                var discoveryDocument = await configurationManager.GetConfigurationAsync(ct);
                var signingKeys = discoveryDocument.SigningKeys;
    
                var validationParameters = new TokenValidationParameters
                {
                    ValidateIssuer = bool.Parse(ProviderInfo.AdditionalParams["ValidateIssuer"]),
                    ValidIssuer = issuer,
                    ValidateIssuerSigningKey = true,
                    IssuerSigningKeys = signingKeys,
                    ValidateLifetime = true,
                    ClockSkew = TimeSpan.FromMinutes(5),
                    ValidateAudience = false
                };
    
                var principal = new JwtSecurityTokenHandler().ValidateToken(token, validationParameters, out var rawValidatedToken);
                principal.AddMappedClaims(ProviderInfo.ClaimMappings);
    
                var audienceClaim = principal.Claims.FirstOrDefault(c => c.Type == "aud");
    
                if (audienceClaim == null)
                {
                    throw new UserFriendlyException("aud claim is missing !");
                }
    
                //Validate clientId
                if (ProviderInfo.ClientId != audienceClaim.Value)
                {
                    throw new ApplicationException("ClientId couldn't verified.");
                }
    
                return new ValidateTokenResult((JwtSecurityToken)rawValidatedToken, principal);
            }
    
            class ValidateTokenResult
            {
                public JwtSecurityToken Token { get; set; }
    
                public ClaimsPrincipal Principal { get; set; }
    
                public ValidateTokenResult(JwtSecurityToken token, ClaimsPrincipal principal)
                {
                    Token = token;
                    Principal = principal;
                }
            }
        }
    
  • User Avatar
    0
    ismcagdas created
    Support Team

    I have created the issue, please follow https://github.com/aspnetzero/aspnet-zero-core/issues/3693. You can delete this class from your project when the related issue is completed.

  • User Avatar
    0
    [email protected] created

    I am trying to get to get an additional value from the token and can't seem to understand how to get the additional values using the code above.

    I can do this in TokenAuthController but it seems like a hack.

            var handler = new JwtSecurityTokenHandler();
            var jwtSecurityToken = handler.ReadJwtToken(model.ProviderAccessCode);
            var oidValue = jwtSecurityToken.Claims.First(claim => claim.Type == "oid").Value;
    
  • User Avatar
    0
    ismcagdas created
    Support Team

    Hi,

    Your approach is fine at the moment because the class I have shared above is in the license packages and can't be overwritten.