ASP.NET Core 9 Razor Pages(含Identity)Web API认证问题
Razor Page调用Web API登录后用户未认证的问题解决
问题背景
基于ASP.NET Core 9 Razor Pages + Identity搭建的项目,同时实现了供第三方客户端调用的Login Minimal API。修改Razor Page登录逻辑后,通过HttpClient调用API返回成功,但当前Page的User.Identity.IsAuthenticated仍为false,必须再次执行SignInManager.PasswordSignInAsync才能完成认证,需要找到正确的实现方式。
核心原因
HttpClient的请求上下文与浏览器的请求上下文完全独立:
- Page中用HttpClient调用API时,是服务器内部发起的全新请求,API里的
SignInManager操作的是这个内部请求的HttpContext,生成的认证Cookie只会写入内部请求的响应,不会传递给浏览器的原始请求。 - 当前Page所在的浏览器请求上下文未被处理认证,因此
User.Identity.IsAuthenticated始终为false。
解决方案
方案1:抽离共享登录逻辑(推荐)
没必要让Page绕路调用API,将登录验证的核心逻辑抽成独立服务,让Page和API复用同一逻辑,既避免重复代码,也解决上下文隔离问题。
1. 创建登录服务
public interface ILoginService { Task<SignInResult> LoginAsync(string userName, string password, bool rememberMe); } public class LoginService : ILoginService { private readonly SignInManager<MyUserType> _signInManager; public LoginService(SignInManager<MyUserType> signInManager) { _signInManager = signInManager; } public async Task<SignInResult> LoginAsync(string userName, string password, bool rememberMe) { return await _signInManager.PasswordSignInAsync(userName, password, rememberMe, lockoutOnFailure: false); } }
2. 注册服务
在Program.cs中注入服务:
builder.Services.AddScoped<ILoginService, LoginService>();
3. 修改Razor Page的OnPostAsync
直接调用共享服务完成当前上下文的认证:
private readonly ILoginService _loginService; private readonly IStringLocalizer<LoginModel> _localizer; // 构造函数注入依赖 public LoginModel(ILoginService loginService, IStringLocalizer<LoginModel> localizer) { _loginService = loginService; _localizer = localizer; } public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); if (ModelState.IsValid) { var result = await _loginService.LoginAsync(Input.UserName, Input.Password, Input.RememberMe); if (result.Succeeded) { return LocalRedirect(returnUrl); } else { ModelState.AddModelError(string.Empty, _localizer["LoginError"].Value); } } return Page(); }
4. 修改Minimal API
同样调用共享服务处理第三方客户端的登录请求:
app.MapPost("/api/LoginUser", async (InputModel input, ILoginService loginService) => { var result = await loginService.LoginAsync(input.UserName, input.Password, input.RememberMe); if (result.Succeeded) { return Results.Ok(); } return Results.BadRequest($"登录失败:{result}"); });
方案2:强制传递认证Cookie(不推荐)
如果必须让Page通过API完成登录,需要提取API响应中的认证Cookie并添加到当前Page的响应中,但这种方式冗余且易出错,仅适合特殊场景:
public async Task<IActionResult> OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); if (ModelState.IsValid) { var cookieContainer = new CookieContainer(); using (var handler = new HttpClientHandler { CookieContainer = cookieContainer }) using (var client = new HttpClient(handler)) { client.BaseAddress = new Uri($"{Request.Scheme}://{Request.Host}{Request.PathBase}"); var response = await client.PostAsJsonAsync("/api/LoginUser", Input); switch (response.StatusCode) { case System.Net.HttpStatusCode.OK: // 提取API返回的认证Cookie var cookies = cookieContainer.GetCookies(client.BaseAddress); foreach (Cookie cookie in cookies) { // 将Cookie添加到当前Page的响应中 Response.Cookies.Append(cookie.Name, cookie.Value, new CookieOptions { Domain = cookie.Domain, Path = cookie.Path, Expires = cookie.Expires, Secure = cookie.Secure, HttpOnly = cookie.HttpOnly, SameSite = SameSiteMode.Lax }); } return Redirect(returnUrl); case System.Net.HttpStatusCode.BadRequest: var errorText = await response.Content.ReadAsStringAsync(); ModelState.AddModelError(string.Empty, errorText); return Page(); } } } return Page(); }
总结
方案1是最佳实践,遵循DRY原则,减少重复代码同时避免上下文隔离问题;方案2仅作为特殊场景的备选,不建议常规项目使用。
内容的提问来源于stack exchange,提问作者bairog
相关产品推荐
相关产品推荐

