You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Blazor Server调用ASP.NET Core API时HttpOnly Cookie无法到达浏览器

问题根源

你的核心问题在于请求链路的层级关系:浏览器并没有直接和API建立HTTP连接,而是通过Blazor Server作为中间代理发起请求。API返回的Set-Cookie头是发送给Blazor Server的HttpClient的,而非直接发送给浏览器,所以浏览器自然接收不到这个Cookie。

另外,Cookie的配置也可能加剧问题:

  • SameSite=Strict:严格限制Cookie仅在同站点请求中发送,而Blazor Server和API是不同端口(localhost:7212 vs localhost:7088),属于不同站点
  • Secure=true:如果你的本地环境使用HTTP而非HTTPS,浏览器会拒绝保存这个Cookie

解决方案

方案1:让浏览器直接调用API

绕过Blazor Server的代理,让浏览器直接向API发起登录请求,这样API的Set-Cookie头会直接发送给浏览器,被浏览器正确保存。

实现方式:使用JS互操作发起请求

修改Login.razor,通过JavaScript直接调用API:

@page "/login"
@inject IJSRuntime _jsRuntime
@inject NavigationManager Navigation

<button @onclick="HandleLogin">Sign In</button>

@code {
    private string _email;
    private string _password;

    private async Task HandleLogin()
    {
        var isAuthenticated = await _jsRuntime.InvokeAsync<bool>("login", _email, _password);
        
        if (isAuthenticated)
            Navigation.NavigateTo("/");
    }
}

在wwwroot/js/site.js中添加登录逻辑:

async function login(email, password) {
    const response = await fetch('https://localhost:7088/api/authentication/login', {
        method: 'POST',
        headers: {
            'Content-Type': 'application/json'
        },
        credentials: 'include', // 确保携带/保存Cookie
        body: JSON.stringify({ email, password })
    });

    return response.ok;
}

方案2:Blazor Server转发Cookie到浏览器

让Blazor Server接收API返回的Set-Cookie头,然后将其转发给浏览器。需要注意跨站点Cookie的配置调整。

实现步骤:

  1. 注入IHttpContextAccessor到你的AuthService中,用于操作浏览器的响应Cookie
  2. 在LoginAsync中提取API响应的Set-Cookie头,转发到浏览器

修改AuthService代码:

public class AuthService : IAuthService
{
    private readonly HttpClient _httpClient;
    private readonly IHttpContextAccessor _httpContextAccessor;

    public AuthService(HttpClient httpClient, IHttpContextAccessor httpContextAccessor)
    {
        _httpClient = httpClient;
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task<LoginResult> LoginAsync(LoginRequestDTO request)
    {
        var response = await _httpClient.PostAsJsonAsync("api/authentication/login", request);
        
        // 提取API的Set-Cookie头
        if (response.Headers.TryGetValues("Set-Cookie", out var setCookieValues))
        {
            var httpContext = _httpContextAccessor.HttpContext;
            if (httpContext != null)
            {
                foreach (var setCookie in setCookieValues)
                {
                    // 解析Cookie内容,调整配置(比如SameSite改为Lax/None,Secure根据环境调整)
                    var cookieParts = setCookie.Split(';');
                    var cookieNameValue = cookieParts[0].Split('=');
                    var cookieOptions = new CookieOptions
                    {
                        HttpOnly = true,
                        Secure = _httpContextAccessor.HttpContext.Request.IsHttps,
                        SameSite = SameSiteMode.Lax, // 调整为Lax或None以支持跨站点
                        Path = "/"
                    };
                    
                    httpContext.Response.Cookies.Append(cookieNameValue[0], cookieNameValue[1], cookieOptions);
                }
            }
        }

        return new LoginResult { IsAuthenticated = response.IsSuccessStatusCode };
    }
}

同时需要在API的Cookie配置中调整SameSite为Lax或None,并确保Domain设置为localhost(本地开发):

[HttpPost("login")]
public IActionResult Login()
{
    Response.Cookies.Append("accessToken", "mytoken", new CookieOptions
    {
        HttpOnly = true,
        Secure = true,
        SameSite = SameSiteMode.Lax, // 修改为Lax
        Domain = "localhost", // 添加Domain
        Path = "/"
    });
    return Ok();
}

方案3:改用Bearer Token(推荐)

放弃Cookie方式,让API返回Bearer Token,Blazor Server将Token存储到浏览器的localStorage或sessionStorage中,后续请求时从浏览器获取Token并添加到请求头。

实现步骤:

  1. 修改API的Login接口,返回Token而非设置Cookie:
[HttpPost("login")]
public IActionResult Login([FromBody] LoginRequestDTO request)
{
    // 验证用户逻辑
    var token = "mytoken"; // 生成JWT Token
    return Ok(new { Token = token });
}
  1. 修改AuthService,调用API获取Token并通过JS互操作存储到浏览器:
public class AuthService : IAuthService
{
    private readonly HttpClient _httpClient;
    private readonly IJSRuntime _jsRuntime;

    public AuthService(HttpClient httpClient, IJSRuntime jsRuntime)
    {
        _httpClient = httpClient;
        _jsRuntime = jsRuntime;
    }

    public async Task<LoginResult> LoginAsync(LoginRequestDTO request)
    {
        var response = await _httpClient.PostAsJsonAsync("api/authentication/login", request);
        var tokenResponse = await response.Content.ReadFromJsonAsync<TokenResponse>();
        
        if (tokenResponse != null)
        {
            // 将Token存储到localStorage
            await _jsRuntime.InvokeVoidAsync("localStorage.setItem", "accessToken", tokenResponse.Token);
            return new LoginResult { IsAuthenticated = true };
        }

        return new LoginResult { IsAuthenticated = false };
    }
}

public class TokenResponse
{
    public string Token { get; set; }
}
  1. 后续API请求时,从浏览器获取Token并添加到Authorization头:
public async Task<SomeData> GetDataAsync()
{
    var token = await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "accessToken");
    _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
    return await _httpClient.GetFromJsonAsync<SomeData>("api/data");
}

总结
  • 如果必须使用Cookie,优先选择方案1(浏览器直接调用API),逻辑最简单且符合HTTP Cookie的工作机制
  • 如果无法绕过Blazor Server代理,可以使用方案2转发Cookie,但需要处理跨站点Cookie的配置问题
  • 方案3(Bearer Token)是更适合Blazor Server这种中间层架构的身份验证方式,避免Cookie的跨站点限制问题

内容的提问来源于stack exchange,提问作者Prashil Shah

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.11 10:36:07