I know they use Cookie or OpenID Connect , But I'd like to Know how Blazor Server and WASM implement Passwordless Login with auth server.
hi maliming how Blazor Server or WASM implement passwordless login. like use a link redrickt to Blazor Server and keep a user login. I had a EmployeeNumberGrantHandler too. ` public class EmployeeNumberGrantHandler : IOpenIddictServerHandler<OpenIddictServerEvents.HandleTokenRequestContext> { public static OpenIddictServerHandlerDescriptor Descriptor { get; } = OpenIddictServerHandlerDescriptor.CreateBuilder<OpenIddictServerEvents.HandleTokenRequestContext>() .UseScopedHandler
private readonly IdentityUserManager _userManager;
public EmployeeNumberGrantHandler(IdentityUserManager userManager)
{
_userManager = userManager;
}
public async ValueTask HandleAsync(OpenIddictServerEvents.HandleTokenRequestContext context)
{
try
{
if (!string.Equals(context.Request.GrantType, "employee_number", StringComparison.Ordinal))
return ;
var empNo = context.Request["employee_number"]?.ToString();
var ts = context.Request["timestamp"]?.ToString();
var signature = context.Request["signature"]?.ToString();
if (string.IsNullOrEmpty(empNo) || string.IsNullOrEmpty(ts) || string.IsNullOrEmpty(signature))
{
context.Reject(OpenIddictConstants.Errors.InvalidRequest, "Missing parameters.");
return ;
}
// 1. 检查时间戳有效性
if (!long.TryParse(ts, out var ticks) ||
DateTime.UtcNow - new DateTime(ticks, DateTimeKind.Utc) > TimeSpan.FromMinutes(5))
{
context.Reject(OpenIddictConstants.Errors.InvalidGrant, "Expired timestamp.");
return ;
}
// 2. 验证签名
var raw = $"{empNo}:{ts}";
if (!VerifySignature(raw, signature, "SuperSecretSharedKey123!"))
{
context.Reject(OpenIddictConstants.Errors.InvalidGrant, "Invalid signature.");
return ;
}
// 3. 查找用户
var user = await _userManager.FindByNameAsync(empNo);
if (user == null)
{
context.Reject(OpenIddictConstants.Errors.InvalidGrant, "User not found.");
return;
}
// 4. 创建 ClaimsIdentity
var identity = new ClaimsIdentity(
TokenValidationParameters.DefaultAuthenticationType,
OpenIddictConstants.Claims.Name,
OpenIddictConstants.Claims.Role);
// subject(必须)
var subject = new Claim(OpenIddictConstants.Claims.Subject, user.Id.ToString());
subject.SetDestinations(OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken);
identity.AddClaim(subject);
// 用户名
var name = new Claim(OpenIddictConstants.Claims.Name, user.UserName ?? empNo);
name.SetDestinations(OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken);
identity.AddClaim(name);
// 工号
var empNoClaim = new Claim("employee_number", empNo);
empNoClaim.SetDestinations(OpenIddictConstants.Destinations.AccessToken,
OpenIddictConstants.Destinations.IdentityToken);
identity.AddClaim(empNoClaim);
// 5. 创建 principal
var principal = new ClaimsPrincipal(identity);
// 给 token 添加 scopes(至少 openid/profile,客户端必须要请求)
principal.SetScopes(new[]
{
OpenIddictConstants.Scopes.OpenId,
OpenIddictConstants.Scopes.Profile,
OpenIddictConstants.Scopes.Email,
OpenIddictConstants.Scopes.Roles
}.Intersect(context.Request.GetScopes()));
// 可以给 access_token 附加 API 资源
principal.SetResources("resource_server");
context.Principal = principal;
context.SignIn(principal);
context.HandleRequest();
}
catch (Exception ex)
{
context.Reject(OpenIddictConstants.Errors.InvalidGrant, ex.Message);
return ;
}
}
private static bool VerifySignature(string raw, string signature, string secretKey)
{
using var hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secretKey));
var hash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(raw));
var expected = Convert.ToBase64String(hash);
return expected == signature;
}
}`
HI: I had my customgrant named EmployeeNumberGrant `public class EmployeeNumberGrant : ITokenExtensionGrant { public const string ExtensionGrantName = "employee_number";
public string Name => ExtensionGrantName;
private IdentityUserManager _userManager;
private IUserClaimsPrincipalFactory<IdentityUser> _claimsFactory;
private AbpOpenIddictClaimsPrincipalManager _claimsPrincipalManager;
private AbpSignInManager _signInManager;
public EmployeeNumberGrant(
IdentityUserManager userManager,
IUserClaimsPrincipalFactory<IdentityUser> claimsFactory,
AbpOpenIddictClaimsPrincipalManager claimsPrincipalManager)
{
_userManager = userManager;
_claimsFactory = claimsFactory;
_claimsPrincipalManager = claimsPrincipalManager;
}
public EmployeeNumberGrant()
{
}
public async Task<IActionResult> HandleAsync(ExtensionGrantContext context)
{
_userManager = context.HttpContext.RequestServices.GetRequiredService<IdentityUserManager>();
_claimsFactory = context.HttpContext.RequestServices.GetRequiredService<IUserClaimsPrincipalFactory<IdentityUser>>();
_claimsPrincipalManager = context.HttpContext.RequestServices.GetRequiredService<AbpOpenIddictClaimsPrincipalManager>();
_signInManager= context.HttpContext.RequestServices.GetRequiredService<AbpSignInManager>();
var empNo = context.Request.GetParameter("employee_number")?.ToString();
var ts = context.Request.GetParameter("timestamp")?.ToString();
var signature = context.Request.GetParameter("signature")?.ToString();
if (string.IsNullOrEmpty(empNo) || string.IsNullOrEmpty(ts) || string.IsNullOrEmpty(signature))
{
return new ForbidResult(new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidRequest,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Missing parameters."
}));
}
// 1. 校验时间戳
if (!long.TryParse(ts, out var ticks) ||
DateTime.UtcNow - new DateTime(ticks, DateTimeKind.Utc) > TimeSpan.FromMinutes(5))
{
return new ForbidResult(new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Expired timestamp."
}));
}
// 2. 校验签名
var raw = $"{empNo}:{ts}";
if (!VerifySignature(raw, signature, "SuperSecretSharedKey123!"))
{
return new ForbidResult(new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "Invalid signature."
}));
}
// 3. 查找用户
var user = await _userManager.FindByNameAsync(empNo);
if (user == null)
{
return new ForbidResult(new[] { OpenIddictServerAspNetCoreDefaults.AuthenticationScheme },
new AuthenticationProperties(new Dictionary<string, string>
{
[OpenIddictServerAspNetCoreConstants.Properties.Error] = OpenIddictConstants.Errors.InvalidGrant,
[OpenIddictServerAspNetCoreConstants.Properties.ErrorDescription] = "User not found."
}));
}
// 4. 创建 ClaimsPrincipal
var principal = await _claimsFactory.CreateAsync(user);
// 附加自定义 Claim
((ClaimsIdentity)principal.Identity!).AddClaim("employee_number", empNo);
// 设置 scopes
var scopes = new[] {"profile", "roles", "email", "phone", "offline_access", "master9" }.ToImmutableArray();
principal.SetScopes(scopes);
// 关联资源
var resources = new List<string>();
await foreach (var resource in context.HttpContext.RequestServices
.GetRequiredService<IOpenIddictScopeManager>()
.ListResourcesAsync(scopes))
{
resources.Add(resource);
}
principal.SetResources(resources);
principal.SetAudiences("Master9");
// 交给 ABP 内置 ClaimsPrincipalManager 处理(角色、权限等)
await _claimsPrincipalManager.HandleAsync(context.Request, principal);
//principal.SetScopes(context.Request.GetScopes());
// await _signInManager.SignInAsync(user, isPersistent: false);
return new SignInResult(OpenIddictServerAspNetCoreDefaults.AuthenticationScheme, principal);
}
private static bool VerifySignature(string raw, string signature, string secretKey)
{
using var hmac = new System.Security.Cryptography.HMACSHA256(System.Text.Encoding.UTF8.GetBytes(secretKey));
var hash = hmac.ComputeHash(System.Text.Encoding.UTF8.GetBytes(raw));
var expected = Convert.ToBase64String(hash);
return expected == signature;
}
}`
now I don't know how to make blazor app login with this grant.
HI I created a module project when I open a entity creat window GUID property doesn't show in the properties select box.
but in the main project abp suite supports GUID type.
through there also a bug with guid type
in SEARCH box

for example,if I want to use indentity user in one entity in my module should I reference Volo.Abp.Identity.pro in each project. or install Volo.Abp.Identity module,so when i chose "Include entities from ABP modules" then I can choses ABP entities.
Volo.Abp.AspNetCore.Mvc, Version=8.1.0.0
hi Can‘t reproduce this in a new template project. it seems in single-layer project the Volo.Abp.AspNetCore.Mvc.dll still references to Microsoft.AspNetCore.Mvc.Versioning
https://docs.abp.io/en/commercial/latest/migration-guides/v8_1#use-asp-versioning-mvc-to-replace-microsoft-aspnetcore-mvc-versi
2024-04-07 11:27:43.110 +08:00 [FTL] Host terminated unexpectedly!
Volo.Abp.AbpInitializationException: An error occurred during ConfigureServicesAsync phase of the module Volo.Abp.AspNetCore.Mvc.AbpAspNetCoreMvcModule, Volo.Abp.AspNetCore.Mvc, Version=8.1.0.0, Culture=neutral, PublicKeyToken=null. See the inner exception for details.
---> System.IO.FileNotFoundException: Could not load file or assembly 'Microsoft.AspNetCore.Mvc.Versioning, Version=5.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'. 系统找不到指定的文件。
File name: 'Microsoft.AspNetCore.Mvc.Versioning, Version=5.1.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60'
at System.ModuleHandle.ResolveType(QCallModule module, Int32 typeToken, IntPtr* typeInstArgs, Int32 typeInstCount, IntPtr* methodInstArgs, Int32 methodInstCount, ObjectHandleOnStack type)
at System.ModuleHandle.ResolveTypeHandle(Int32 typeToken, RuntimeTypeHandle[] typeInstantiationContext, RuntimeTypeHandle[] methodInstantiationContext)
at System.Reflection.RuntimeModule.ResolveType(Int32 metadataToken, Type[] genericTypeArguments, Type[] genericMethodArguments)
at System.Reflection.CustomAttribute.FilterCustomAttributeRecord(MetadataToken caCtorToken, MetadataImport& scope, RuntimeModule decoratedModule, MetadataToken decoratedToken, RuntimeType attributeFilterType, Boolean mustBeInheritable, ListBuilder1& derivedAttributes, RuntimeType& attributeType, IRuntimeMethodInfo& ctorWithParameters, Boolean& isVarArg) at System.Reflection.CustomAttribute.IsCustomAttributeDefined(RuntimeModule decoratedModule, Int32 decoratedMetadataToken, RuntimeType attributeFilterType, Int32 attributeCtorToken, Boolean mustBeInheritable) at System.Reflection.CustomAttribute.IsDefined(RuntimeType type, RuntimeType caType, Boolean inherit) at System.Attribute.IsDefined(MemberInfo element, Type attributeType, Boolean inherit) at Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider.IsController(TypeInfo typeInfo) at Microsoft.AspNetCore.Mvc.Controllers.ControllerFeatureProvider.PopulateFeature(IEnumerable1 parts, ControllerFeature feature)
at Microsoft.AspNetCore.Mvc.ApplicationParts.ApplicationPartManager.PopulateFeature[TFeature](TFeature feature)
at Microsoft.Extensions.DependencyInjection.MvcCoreMvcBuilderExtensions.AddControllersAsServices(IMvcBuilder builder)
at Volo.Abp.AspNetCore.Mvc.AbpAspNetCoreMvcModule.ConfigureServices(ServiceConfigurationContext context)
at Volo.Abp.Modularity.AbpModule.ConfigureServicesAsync(ServiceConfigurationContext context)
at Volo.Abp.AbpApplicationBase.ConfigureServicesAsync()
--- End of inner exception stack trace ---
at Volo.Abp.AbpApplicationBase.ConfigureServicesAsync()
at Volo.Abp.AbpApplicationFactory.CreateAsync[TStartupModule](IServiceCollection services, Action1 optionsAction) at Microsoft.Extensions.DependencyInjection.ServiceCollectionApplicationExtensions.AddApplicationAsync[TStartupModule](IServiceCollection services, Action1 optionsAction)
at Microsoft.Extensions.DependencyInjection.WebApplicationBuilderExtensions.AddApplicationAsync[TStartupModule](WebApplicationBuilder builder, Action`1 optionsAction)
at Vote.Blazor.Program.Main(String[] args) in F:\SynologyDrive\项目_代码\Vote8.1\src\Vote.Blazor\Program.cs:line 44
I 'v already cheked this pots. it doesn't work.