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

如何在ASP.NET Core Web API中验证Supabase JWT令牌?解决受众验证失败问题

解决Supabase Auth与ASP.NET Core API的JWT验证问题(IDX10214受众不匹配)

一、错误原因分析

IDX10214错误是JWT受众(aud字段)验证失败,核心矛盾点:

  • Supabase签发的Access Token默认aud值为你的Supabase项目Project Ref(可在控制台「设置→API」页面查看)
  • ASP.NET Core默认验证的aud是API自身标识,两者不匹配导致验证拦截

二、本地JWT验证(基于Signing Keys)的正确配置

直接修改Program.cs中的JWT验证逻辑,指定匹配的受众、签发方,并通过Supabase的JWKS端点获取签名密钥完成本地验证:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;

var builder = WebApplication.CreateBuilder(args);

// 注册内存缓存,用于缓存JWKS密钥避免重复请求
builder.Services.AddMemoryCache();

// 配置JWT认证
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    {
        var supabaseProjectId = builder.Configuration["Supabase:ProjectId"]; // 从配置文件读取项目ID
        var supabaseAuthBaseUrl = $"https://{supabaseProjectId}.supabase.co/auth/v1";

        options.Authority = supabaseAuthBaseUrl;
        options.Audience = supabaseProjectId; // 必须与Supabase Token的`aud`字段完全一致(即Project Ref)

        options.TokenValidationParameters = new TokenValidationParameters
        {
            ValidateIssuer = true,
            ValidIssuer = supabaseAuthBaseUrl, // 匹配Supabase Token的`iss`字段
            ValidateAudience = true,
            ValidAudience = supabaseProjectId,
            ValidateLifetime = true,
            ValidateIssuerSigningKey = true,
            // 从Supabase JWKS端点拉取签名密钥并缓存
            IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) =>
            {
                var cache = builder.Services.BuildServiceProvider().GetRequiredService<IMemoryCache>();
                var jwks = cache.GetOrCreate("SupabaseJWKS", entry =>
                {
                    entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromHours(24); // Supabase密钥不会频繁变更,缓存24小时
                    using var httpClient = new HttpClient();
                    return httpClient.GetFromJsonAsync<JsonWebKeySet>($"{supabaseAuthBaseUrl}/jwks").Result;
                });
                return jwks?.GetSigningKeys();
            }
        };
    });

// 启用授权中间件
builder.Services.AddAuthorization();

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization();

// 配置API路由
app.MapControllers();

app.Run();

关键配置说明

  1. ValidAudience:必须设置为Supabase的Project Ref,可通过jwt.io解析前端获取的Access Token,确认aud字段值后对应配置
  2. ValidIssuer:固定为https://<你的项目ID>.supabase.co/auth/v1,与Token的iss字段完全匹配
  3. JWKS缓存:通过内存缓存减少重复请求Supabase的JWKS端点,提升验证性能

三、快速排查步骤

  1. 解析前端的Access Token,确认aud和iss字段的实际值
  2. 检查Program.cs中ValidAudience、ValidIssuer是否与Token字段完全一致
  3. 确认前端传递的是Access Token,而非Refresh Token(Refresh Token的aud字段不同,无法用于API验证)

四、更优对接方案对比

方案1:本地JWT验证(推荐)

  • 优点:无需依赖Supabase验证接口,验证速度快,适合高并发场景
  • 缺点:需处理密钥缓存,配置稍复杂

方案2:远程调用Supabase验证端点

如果不想处理本地密钥管理,可直接调用Supabase的/auth/v1/verify接口验证Token,适合低并发场景:

using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;
using System.Text.Json;

public class SupabaseRemoteAuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
{
    private readonly HttpClient _httpClient;
    private readonly string _supabaseProjectId;

    public SupabaseRemoteAuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options, 
        ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, 
        HttpClient httpClient, IConfiguration configuration)
        : base(options, logger, encoder, clock)
    {
        _httpClient = httpClient;
        _supabaseProjectId = configuration["Supabase:ProjectId"];
    }

    protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
    {
        if (!Request.Headers.TryGetValue("Authorization", out var authHeaderValues))
            return AuthenticateResult.Fail("缺少Authorization请求头");

        var authHeader = authHeaderValues.ToString();
        if (!authHeader.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
            return AuthenticateResult.Fail("无效的授权方案");

        var token = authHeader["Bearer ".Length..].Trim();

        try
        {
            var requestContent = new FormUrlEncodedContent(new Dictionary<string, string>
            {
                {"token", token},
                {"type", "access_token"}
            });

            var response = await _httpClient.PostAsync($"https://{_supabaseProjectId}.supabase.co/auth/v1/verify", requestContent);
            if (!response.IsSuccessStatusCode)
                return AuthenticateResult.Fail("Token验证失败");

            var tokenPayload = await JsonSerializer.DeserializeAsync<Dictionary<string, object>>(await response.Content.ReadAsStreamAsync());
            var claims = new List<Claim>
            {
                new Claim(ClaimTypes.NameIdentifier, tokenPayload["sub"].ToString()),
                new Claim(ClaimTypes.Email, tokenPayload["email"].ToString())
                // 根据业务需求添加其他Claim
            };

            var identity = new ClaimsIdentity(claims, Scheme.Name);
            var principal = new ClaimsPrincipal(identity);
            var ticket = new AuthenticationTicket(principal, Scheme.Name);

            return AuthenticateResult.Success(ticket);
        }
        catch (Exception ex)
        {
            return AuthenticateResult.Fail($"验证出错:{ex.Message}");
        }
    }
}

// 在Program.cs中注册该认证方案
builder.Services.AddAuthentication("SupabaseRemote")
    .AddScheme<AuthenticationSchemeOptions, SupabaseRemoteAuthHandler>("SupabaseRemote", options => { });

// 启用授权
builder.Services.AddAuthorization();

// 应用中间件
app.UseAuthentication();
app.UseAuthorization();
  • 优点:实现简单,无需处理密钥和缓存逻辑
  • 缺点:每次请求都要调用Supabase接口,增加延迟和依赖风险

内容的提问来源于stack exchange,提问作者Gabriel Goranov

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 09:13:20