- 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)
-
0
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.
-
0
Sent, kindly check.
-
0
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)
-
0
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";
}
-
0
hi
You can add the
scope
to yourTokenRequest
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" } } });