Blazor Web App中无法获取AccessToken的问题排查
核心问题
通过SignInManager.PasswordSignInAsync完成用户名/密码登录后,在ReturnToMobileApp组件中调用auth.Properties.GetTokenValue("access_token")始终返回null,尽管已添加.AddBearerToken配置使Postman能通过API端点获取Token。
原因分析
PasswordSignInAsync是基于Cookie的认证流程,仅负责创建Cookie会话,默认不会生成Bearer Token,也不会将Token存储到认证会话的Properties中。而.AddBearerToken仅为Identity的API端点(如/login)提供Token生成能力,不会自动关联到Cookie登录会话。
解决方案
需要在登录成功后手动生成Bearer Token,并将Token存储到认证会话的Properties中,这样后续就能在组件中通过GetTokenValue获取。
步骤1:确保服务配置正确
在Program.cs中确认已添加AddBearerToken,并保留Identity的完整配置:
services.AddAuthorizationCore(); services.AddCascadingAuthenticationState(); services.AddIdentity<IdentityUser, IdentityRole>(options => { options.SignIn.RequireConfirmedAccount = false; }) .AddEntityFrameworkStores<MyIdentityDbContext>() .AddSignInManager() .AddDefaultUI() .AddDefaultTokenProviders() .AddApiEndpoints() // 添加Bearer Token生成配置 .AddBearerToken(IdentityConstants.BearerScheme, o => { o.BearerTokenExpiration = TimeSpan.FromMinutes(5); }); // 其他Identity配置... services.ConfigureApplicationCookie(options => { options.Cookie.Name = "MyApp.Cookie"; options.Cookie.HttpOnly = true; options.ExpireTimeSpan = TimeSpan.FromMinutes(5); options.LoginPath = "/Account/Login"; options.AccessDeniedPath = "/Account/AccessDenied"; options.SlidingExpiration = true; });
步骤2:修改登录逻辑,手动生成并存储Token
替换原来的PasswordSignInAsync,改为先验证用户密码,再生成Token并关联到Cookie会话:
[Inject] public required SignInManager<IdentityUser> SignInManager { get; set; } [Inject] public required IBearerTokenGenerator BearerTokenGenerator { get; set; } [Inject] public required IdentityRedirectManager RedirectManager { get; set; } [Inject] public required ILogger<Login> Logger { get; set; } // ... 其他代码(如Input模型) var user = await SignInManager.UserManager.FindByEmailAsync(Input.Email); if (user != null) { var passwordCheckResult = await SignInManager.CheckPasswordSignInAsync(user, Input.Password, lockoutOnFailure: false); if (passwordCheckResult.Succeeded) { // 生成Bearer Token var tokenResult = await BearerTokenGenerator.GenerateAsync(user); // 创建认证Properties,存储Token和过期时间 var authProperties = new AuthenticationProperties { IsPersistent = Input.RememberMe, ExpiresUtc = tokenResult.ExpiresUtc }; // 将Access Token和Refresh Token存入Properties authProperties.StoreTokens(new[] { new AuthenticationToken { Name = "access_token", Value = tokenResult.AccessToken }, new AuthenticationToken { Name = "refresh_token", Value = tokenResult.RefreshToken } }); // 执行登录,将Properties关联到Cookie会话 await SignInManager.SignInAsync(user, authProperties); Logger.LogInformation("User logged in successfully."); RedirectManager.RedirectTo("Account/ReturnToMobileApp"); } else { // 处理登录失败逻辑 ModelState.AddModelError(string.Empty, "Invalid login attempt."); } } else { ModelState.AddModelError(string.Empty, "No user found with this email."); }
步骤3:验证组件获取Token
修改后的登录流程会将Token存储到认证会话的Properties中,此时ReturnToMobileApp组件中的auth.Properties.GetTokenValue("access_token")就能正常获取到值:
protected override async Task OnInitializedAsync() { var auth = await HttpContext.AuthenticateAsync(IdentityConstants.ApplicationScheme); var accessToken = auth.Properties.GetTokenValue("access_token"); // 后续逻辑... }
补充说明
Postman能获取Token是因为直接调用了Identity的API端点(如POST /login),这些端点由AddApiEndpoints()和AddBearerToken()提供,会直接返回Token;而Cookie登录流程需要手动关联Token到会话,才能在后续组件中读取。
内容的提问来源于stack exchange,提问作者Ryan Langton

