Open Closed

Issue with Internal Login after Upgrading from IdentityServer to OpenIdDict #6920


User avatar
0
Dina created
  • ABP Framework version: v7.4
  • UI Type: MVC
  • Database System: EF Core (SQL Server)
  • Steps to reproduce the issue:

Hi,

We recently upgraded our authentication system from IdentityServer to OpenIdDict. However, we're currently facing an issue with internal login. After the upgrade, when we request a token and successfully validate it, the "Current User" in our web application shows as authenticated. However, when we try to retrieve data from the application project, the "Current User" is not authenticated.

Here's the code snippet we're using for authentication:

  public async Task<AuthenticateResult> Authenticate(string phoneNumber, string code)
  {
      var disc = await httpClient.GetDiscoveryDocumentAsync(_options.CurrentValue.Authority);
      var request = new TokenRequest
      {
          Address = _options.CurrentValue.Authority + _options.CurrentValue.TokenEndPoint,
          GrantType = Re3aytak.Identity.PhoneNumberLoginConsts.GrantType,
          ClientId = _options.CurrentValue.ClientId,
          ClientSecret = _options.CurrentValue.ClientSecret,
          Parameters =
          {
              {"phone_number", phoneNumber},
              {"code", code}
          }
      };
      var configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{_options.CurrentValue.Authority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
      var openidconfig =  await configManager.GetConfigurationAsync();
      var result = await httpClient.RequestTokenAsync(request);
      // success branch
      // generate authTicket
      // authenticate the request
      if (!result.IsError)
      {
          var TokenValidationParameters = new TokenValidationParameters()
          {
              ValidateAudience = false,
              ValidateIssuer = true,
              ValidIssuers = new[] { $"{_options.CurrentValue.Authority}/" },
              ValidIssuer= $"{_options.CurrentValue.Authority}/",
              ValidateIssuerSigningKey = true,
              IssuerSigningKeys = openidconfig.SigningKeys,
              RequireExpirationTime = true,
              ValidateLifetime = true,
              RequireSignedTokens = true,
          };
          JwtSecurityTokenHandler jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
          var claimsPrinciples = jwtSecurityTokenHandler.ValidateToken(result.AccessToken, 
                                                                      TokenValidationParameters, 
                                                                      out SecurityToken validatedToken);


          var AuthenticationProperties = new AuthenticationProperties();
          if (_options.CurrentValue.SaveTokens)
          {
              var authTokens = new List<AuthenticationToken>();

              authTokens.Add(new AuthenticationToken { Name = "access_token", Value = result.AccessToken });
              if (!string.IsNullOrEmpty(result.RefreshToken))
              {
                  authTokens.Add(new AuthenticationToken { Name = "refresh_token", Value = result.RefreshToken });
              }

              if (!string.IsNullOrEmpty(result.TokenType))
              {
                  authTokens.Add(new AuthenticationToken { Name = "token_type", Value = result.TokenType });
              }

              if (result.ExpiresIn != 0)
              {
                  var expiresAt = (DateTime.UtcNow + TimeSpan.FromSeconds(result.ExpiresIn)).AddDays(1);
                  authTokens.Add(new AuthenticationToken
                  {
                      Name = "expires_at",
                      Value = expiresAt.ToString("o", CultureInfo.InvariantCulture)
                  });
                  AuthenticationProperties.ExpiresUtc = expiresAt;
              }
              AuthenticationProperties.StoreTokens(authTokens);
          }

          // generate AuthenticationTicket from the Identity
          // and current authentication scheme
          var ticket = new AuthenticationTicket(claimsPrinciples,AuthenticationProperties, "Cookies");
          // pass on the ticket to the middleware
          return AuthenticateResult.Success(ticket);
      }
      return AuthenticateResult.Fail(result.Error);

  }

Here's a code from web project

var result = await _passwordlessAuthenticator.Authenticate(phonenumber, code);
if (result.Succeeded)
{
    await HttpContext.SignInAsync("Cookies", result.Principal, result.Properties);
    HttpContext.User = result.Principal;

}

5 Answer(s)
  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    when we try to retrieve data from the application project, the "Current User" is not authenticated.

    Please share the full logs of AuthServer and API applications.

    liming.ma@volosoft.com

    Thanks.

  • User Avatar
    0
    Dina created

    Sent, kindly check.

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    Your access_token lack/mismatches the audiences

    Please share the implementation of Re3aytak.Identity.PhoneNumberLoginConsts.GrantType,

    [INF] Request starting HTTP/1.1 GET https://localhost:44397/api/app/country/country-codes?onlyEnabled=True&api-version=1.0 - -
    
    [INF] Failed to validate the token.
    Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10206: Unable to validate audience. The 'audiences' parameter is empty.
    
    [INF] Request starting HTTP/1.1 GET https://localhost:44397/api/app/notification/notifications-count?api-version=1.0 - -
    [INF] Failed to validate the token.
    Microsoft.IdentityModel.Tokens.SecurityTokenInvalidAudienceException: IDX10214: Audience validation failed. Audiences: 'Re3aytak, Public'. Did not match: validationParameters.ValidAudience: 'Re3aytak_Public_Web_Tiered' or validationParameters.ValidAudiences: 'null'.
       at Microsoft.IdentityModel.Tokens.Validators.ValidateAudience(IEnumerable`1 audiences, SecurityToken securityToken, TokenValidationParameters validationParameters)
    
  • User Avatar
    0
    Dina created

    Hi,

    Here's the code snippet we're using

    public class PhoneNumberLoginTokenExtensionGrant : ITokenExtensionGrant, ITransientDependency { public const string ExtensionGrantName = PhoneNumberLoginConsts.GrantType;

      public string Name => ExtensionGrantName;
      public virtual async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
      {
          var identityOptions = context.HttpContext.RequestServices.GetRequiredService<IOptions<IdentityOptions>>();
          var identityUserManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();
          var signInManager = context.HttpContext.RequestServices.GetRequiredService<SignInManager<IdentityUser>>();
          var scopeManager = context.HttpContext.RequestServices.GetRequiredService<IOpenIddictScopeManager>();
          var abpOpenIddictClaimsPrincipalManager = context.HttpContext.RequestServices
              .GetRequiredService<AbpOpenIddictClaimsPrincipalManager>();
          var identitySecurityLogManager =
              context.HttpContext.RequestServices.GetRequiredService<IdentitySecurityLogManager>();
          //var localizer = context.HttpContext.RequestServices
          //    .GetRequiredService<IStringLocalizer<PhoneNumberLoginResource>>();
          var uniquePhoneNumberIdentityUserRepository = context.HttpContext.RequestServices
              .GetRequiredService<IRe3aytakIdentityUserRepository>();
    
          await identityOptions.SetAsync();
    
          var phoneNumber = context.Request.GetParameter("phone_number")?.ToString();
          var code = context.Request.GetParameter("code").ToString();
          var password = context.Request.GetParameter("password").ToString();
    
          if (string.IsNullOrWhiteSpace(phoneNumber))
          {
              return new ForbidResult(
                  new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                  properties: new AuthenticationProperties(new Dictionary<string, string>
                  {
                      [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                          OpenIddictConstants.Errors.InvalidRequest,
    
                      [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                         "localizer[PhoneNumberLoginErrorCodes.InvalidPhoneNumberOrPassword]"
                  }!));
          }
    
          if (string.IsNullOrWhiteSpace(code) && string.IsNullOrEmpty(password))
          {
              return new ForbidResult(
                  new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                  properties: new AuthenticationProperties(new Dictionary<string, string>
                  {
                      [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                          OpenIddictConstants.Errors.InvalidRequest,
    
                      [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                          "localizer[PhoneNumberLoginErrorCodes.InvalidCredential]"
                  }!));
          }
    
          //var identityUser =
          //    await uniquePhoneNumberIdentityUserRepository.GetByConfirmedPhoneNumberAsync(phoneNumber);
    
    
          var identityUser = await uniquePhoneNumberIdentityUserRepository.GetByConfirmedPhoneNumberAsync(phoneNumber);              
    
          if (password.IsNullOrWhiteSpace())
          {
              var result = await identityUserManager.VerifyUserTokenAsync(identityUser,
                  TokenOptions.DefaultPhoneProvider, PhoneNumberLoginConsts.LoginPurposeName, code);
    
              if (!result)
              {
                  return new ForbidResult(
                      new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                      properties: new AuthenticationProperties(new Dictionary<string, string>
                      {
                          [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                              OpenIddictConstants.Errors.InvalidGrant,
    
                          [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                              "localizer[PhoneNumberLoginErrorCodes.InvalidVerificationCode]"
                      }!));
              }
          }
          else
          {
              bool result = await identityUserManager.CheckPasswordAsync(identityUser, password);
    
              if (!result)
              {
                  return new ForbidResult(
                      new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
                      properties: new AuthenticationProperties(new Dictionary<string, string>
                      {
                          [OpenIddictServerAspNetCoreConstants.Properties.Error] =
                              OpenIddictConstants.Errors.InvalidGrant,
    
                          [OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] =
                              "localizer[PhoneNumberLoginErrorCodes.InvalidPhoneNumberOrPassword]"
                      }!));
              }
          }
    
          var principal = await signInManager.CreateUserPrincipalAsync(identityUser);
    
          principal.SetScopes(context.Request.GetScopes());
          principal.SetResources(await GetResourcesAsync(context.Request.GetScopes(), scopeManager));
    
          await abpOpenIddictClaimsPrincipalManager.HandleAsync(context.Request, principal);
    
          await identitySecurityLogManager.SaveAsync(
              new IdentitySecurityLogContext
              {
                  Identity = OpenIddictSecurityLogIdentityConsts.OpenIddict,
                  Action = OpenIddictSecurityLogActionConsts.LoginSucceeded,
                  UserName = context.Request.Username,
                  ClientId = context.Request.ClientId
              }
          );
    
          return new Microsoft.AspNetCore.Mvc.SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme,
              principal);
      }
    
         protected virtual async Task<IEnumerable<string>> GetResourcesAsync(ImmutableArray<string> scopes,
          IOpenIddictScopeManager scopeManager)
      {
          var resources = new List<string>();
          if (!scopes.Any())
          {
              return resources;
          }
    
          await foreach (var resource in scopeManager.ListResourcesAsync(scopes))
          {
              resources.Add(resource);
          }
    
          return resources;
      }
    

    }

    And the Re3aytakDomainModule configurations

    public override void ConfigureServices(ServiceConfigurationContext context) {
    Configure<AbpOpenIddictExtensionGrantsOptions>(options => { options.Grants.Add(PhoneNumberLoginConsts.GrantType, new PhoneNumberLoginTokenExtensionGrant()); }); }

    And here's the PhoneNumberLoginConsts

    public static class PhoneNumberLoginConsts { public static int MaxVerificationCodeLength { get; set; } = 8;

       public const string GrantType = "PhoneNumberLogin_credentials";
    
       public const string IdentityServerHttpClientName = "EasyAbpAbpPhoneNumberLogin"; // default client
    
       public const string VerificationCodeCachePrefix = "PhoneNumberLoginVerificationCode";
    
       public const string LoginPurposeName = "LoginByPhoneNumber"; 
    
       public const string ConfirmPurposeName = "ConfirmPhoneNumber";
    

    }

  • User Avatar
    0
    maliming created
    Support Team Fullstack Developer

    hi

    You can add the scope to your TokenRequest parameter.

    https://github.com/abpframework/abp/blob/dev/templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Web.Host/MyProjectNameWebModule.cs#L168

    Example:

    ar client = new HttpClient();
    
    var response = await client.RequestTokenAsync(new TokenRequest
    {
        Address = "https://demo.identityserver.io/connect/token",
        GrantType = "custom",
    
        ClientId = "client",
        ClientSecret = "secret",
    
        Parameters =
        {
            { "custom_parameter", "custom value"},
            { "scope", "api1" }
        }
    });
    
Made with ❤️ on ABP v9.1.0-preview. Updated on November 11, 2024, 11:11