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";
}
Sent, kindly check.
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;
}
hi
The framework will add it to access_token automatically. That is, if you don't change the code, it will be there.
https://github.com/abpframework/abp/blob/dev/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs#L61
also, i have overridden the AbpUserClaimsPrincipalFactory
using System; using System.Linq; using System.Security.Claims; using System.Threading.Tasks; using Microsoft.AspNetCore.Identity; using Microsoft.Extensions.Options; using System.Security.Principal; using Volo.Abp.DependencyInjection; using Volo.Abp.Security.Claims; using Volo.Abp.Uow; using MyApp.Users; using Volo.Abp.Identity; using IdentityUser = Volo.Abp.Identity.IdentityUser; using IdentityRole = Volo.Abp.Identity.IdentityRole; namespace MyApp.Identity { /// <summary> /// </summary> [Dependency(ReplaceServices = true)] [ExposeServices(typeof(AbpUserClaimsPrincipalFactory), typeof(ExtendedUserClaimsPrincipalFactory), IncludeSelf = false, IncludeDefaults = false)] public class ExtendedUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory, ITransientDependency { public ExtendedUserClaimsPrincipalFactory( UserManager<IdentityUser> userManager, RoleManager<IdentityRole> roleManager, IOptions<IdentityOptions> options, ICurrentPrincipalAccessor currentPrincipalAccessor, IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory) : base( userManager, roleManager, options, currentPrincipalAccessor, abpClaimsPrincipalFactory) { } [UnitOfWork] public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user) { var principal = await base.CreateAsync(user); var identity = principal.Identities.First(); if (user.TenantId.HasValue) { identity.AddIfNotContains(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString())); } if (!user.Name.IsNullOrWhiteSpace()) { identity.AddIfNotContains(new Claim(AbpClaimTypes.Name, user.Name)); } if (!user.Surname.IsNullOrWhiteSpace()) { identity.AddIfNotContains(new Claim(AbpClaimTypes.SurName, user.Surname)); } if (!user.PhoneNumber.IsNullOrWhiteSpace()) { identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber)); } identity.AddIfNotContains( new Claim(AbpClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed.ToString())); if (!user.Email.IsNullOrWhiteSpace()) { identity.AddIfNotContains(new Claim(AbpClaimTypes.Email, user.Email)); } identity.AddIfNotContains(new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString())); if (user.ExtraProperties.ContainsKey(AppUserConsts.UserTypePropertyName)) { var userType = user.ExtraProperties[AppUserConsts.UserTypePropertyName]?.ToString(); if (!string.IsNullOrEmpty(userType)) identity.AddIfNotContains(new Claim(AppUserConsts.UserTypePropertyName, userType)); } using (CurrentPrincipalAccessor.Change(identity)) { await AbpClaimsPrincipalFactory.CreateAsync(principal); } return principal; } } }
and configured at the domain module
PreConfigure<IdentityBuilder>(builder => { builder.AddClaimsPrincipalFactory<ExtendedUserClaimsPrincipalFactory>(); });
and the principal is filled correctly with claims values, but the phoneNumber at currentUser is still null and currentUser claims did not update with the values at the principal.
Please advise.
and what about this issue please? the claims are filled correctly at the principal but the currentUser still does not contain them.
hi
IDX10206: Unable to validate audience. The 'audiences' parameter is empty.
Please share a
access_token
do you mean a generated access_token value or what?
any update please?
hi
The framework will add it to access_token automatically. That is, if you don't change the code, it will be there.
https://github.com/abpframework/abp/blob/dev/modules/identity/src/Volo.Abp.Identity.Domain/Volo/Abp/Identity/AbpUserClaimsPrincipalFactory.cs#L61
also, i have overridden the AbpUserClaimsPrincipalFactory
using System;
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Identity;
using Microsoft.Extensions.Options;
using System.Security.Principal;
using Volo.Abp.DependencyInjection;
using Volo.Abp.Security.Claims;
using Volo.Abp.Uow;
using MyApp.Users;
using Volo.Abp.Identity;
using IdentityUser = Volo.Abp.Identity.IdentityUser;
using IdentityRole = Volo.Abp.Identity.IdentityRole;
namespace MyApp.Identity
{
/// <summary>
/// </summary>
[Dependency(ReplaceServices = true)]
[ExposeServices(typeof(AbpUserClaimsPrincipalFactory), typeof(ExtendedUserClaimsPrincipalFactory), IncludeSelf = false, IncludeDefaults = false)]
public class ExtendedUserClaimsPrincipalFactory : AbpUserClaimsPrincipalFactory, ITransientDependency
{
public ExtendedUserClaimsPrincipalFactory(
UserManager<IdentityUser> userManager,
RoleManager<IdentityRole> roleManager,
IOptions<IdentityOptions> options,
ICurrentPrincipalAccessor currentPrincipalAccessor,
IAbpClaimsPrincipalFactory abpClaimsPrincipalFactory)
: base(
userManager,
roleManager,
options,
currentPrincipalAccessor,
abpClaimsPrincipalFactory)
{ }
[UnitOfWork]
public override async Task<ClaimsPrincipal> CreateAsync(IdentityUser user)
{
var principal = await base.CreateAsync(user);
var identity = principal.Identities.First();
if (user.TenantId.HasValue)
{
identity.AddIfNotContains(new Claim(AbpClaimTypes.TenantId, user.TenantId.ToString()));
}
if (!user.Name.IsNullOrWhiteSpace())
{
identity.AddIfNotContains(new Claim(AbpClaimTypes.Name, user.Name));
}
if (!user.Surname.IsNullOrWhiteSpace())
{
identity.AddIfNotContains(new Claim(AbpClaimTypes.SurName, user.Surname));
}
if (!user.PhoneNumber.IsNullOrWhiteSpace())
{
identity.AddIfNotContains(new Claim(AbpClaimTypes.PhoneNumber, user.PhoneNumber));
}
identity.AddIfNotContains(
new Claim(AbpClaimTypes.PhoneNumberVerified, user.PhoneNumberConfirmed.ToString()));
if (!user.Email.IsNullOrWhiteSpace())
{
identity.AddIfNotContains(new Claim(AbpClaimTypes.Email, user.Email));
}
identity.AddIfNotContains(new Claim(AbpClaimTypes.EmailVerified, user.EmailConfirmed.ToString()));
if (user.ExtraProperties.ContainsKey(AppUserConsts.UserTypePropertyName))
{
var userType = user.ExtraProperties[AppUserConsts.UserTypePropertyName]?.ToString();
if (!string.IsNullOrEmpty(userType))
identity.AddIfNotContains(new Claim(AppUserConsts.UserTypePropertyName, userType));
}
using (CurrentPrincipalAccessor.Change(identity))
{
await AbpClaimsPrincipalFactory.CreateAsync(principal);
}
return principal;
}
}
}
and configured at the domain module
PreConfigure<IdentityBuilder>(builder =>
{
builder.AddClaimsPrincipalFactory<ExtendedUserClaimsPrincipalFactory>();
});
and the principal is filled correctly with claims values, but the phoneNumber at currentUser is still null and currentUser claims did not update with the values at the principal.
Please advise.
hi,
I have added the customTokenExtensionGrant and now the result is succeed
var request = new TokenRequest
{
Address = _options.CurrentValue.Authority + _options.CurrentValue.TokenEndPoint,
GrantType = MyApp.Identity.PhoneNumberLoginConsts.GrantType,
ClientId = _options.CurrentValue.ClientId,
ClientSecret = _options.CurrentValue.ClientSecret,
Parameters =
{
{"phone_number", phoneNumber},
{"code", code}
}
};
var result = await httpClient.RequestTokenAsync(request);
but there is an old code (while using identity server) after the result is get
if (!result.IsError)
{
var TokenValidationParameters = new TokenValidationParameters()
{
ValidateAudience = true,
ValidAudience = "Public",
ValidateIssuer = true,
ValidIssuers = new[] { _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);
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");
return AuthenticateResult.Success(ticket);
bur there is an error
IDX10206: Unable to validate audience. The 'audiences' parameter is empty.
so how can i fix, or what is the replacement for that code using openIdDict not identityServer?
Hello, would it be any sooner or will it be after awhile, so I need to fix it locally?
hi what is the release date of version 3 please?