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

ASP.NET Core 3.1集成LinkedIn外部登录时GetExternalLoginInfoAsync返回null

解决ASP.NET Core 3.1中LinkedIn外部登录GetExternalLoginInfoAsync()返回null的问题

这个问题我之前帮不少开发者排查过,GetExternalLoginInfoAsync()返回null大多是因为OAuth流程里的用户信息映射没适配LinkedIn的API规则,或者权限、回调配置有疏漏。咱们一步步来修复:

1. 升级到LinkedIn v2 API并配置字段映射

你当前用的是v1版本的用户信息端点,LinkedIn已经逐步废弃v1接口,而且v2的响应格式和v1完全不同,默认的OAuth处理逻辑没法自动解析。

首先把用户信息端点换成v2的:

options.UserInformationEndpoint = "https://api.linkedin.com/v2/userinfo";

然后添加Claim映射规则,把LinkedIn返回的字段对应到ASP.NET的Claim类型:

// 映射LinkedIn返回的核心用户字段到Claims
options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
options.ClaimActions.MapJsonKey("urn:linkedin:picture", "picture");

最后要自定义OnCreatingTicket事件,因为LinkedIn v2 API要求请求头必须指定application/json格式:

options.Events = new OAuthEvents
{
    OnCreatingTicket = async context =>
    {
        var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
        request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
        request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

        var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
        response.EnsureSuccessStatusCode();

        var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
        context.RunClaimActions(user);
    }
};

2. 精简不必要的Scope权限

你配置了w_member_social权限,但这个是用来发布内容到LinkedIn的,如果你只是做登录验证,完全不需要这个权限。多余的权限不仅会增加授权弹窗的复杂度,还可能导致权限验证失败,建议只保留必要的:

options.Scope.Add("r_liteprofile");
options.Scope.Add("r_emailaddress");

3. 确认LinkedIn应用的回调URL完全匹配

在LinkedIn开发者后台的应用设置里,授权回调URL必须和你代码里的CallbackPath完全一致,包括协议(http/https)、端口、路径。比如你的站点是https://localhost:5001,回调URL就必须是https://localhost:5001/signin-linkedin,差一个字符都不行。

4. 检查Cookie和中间件配置

ASP.NET Core的外部登录依赖Cookie保存中间状态,确保你的Cookie配置正确,并且中间件顺序没错:

  • 在ConfigureServices里确认Cookie配置:
    services.ConfigureApplicationCookie(options =>
    {
        options.LoginPath = "/Account/Login";
        options.LogoutPath = "/Account/Logout";
        options.AccessDeniedPath = "/Account/AccessDenied";
    });
    
  • 在Configure方法里确保中间件顺序正确:
    app.UseAuthentication(); // 必须在UseAuthorization之前
    app.UseAuthorization();
    

完整的配置示例

把上面的修改整合后,完整的LinkedIn OAuth配置代码如下:

services.AddAuthentication()
    .AddOAuth("LinkedIn", "LinkedIn", options =>
    {
        var linkedinAuthNSection = Configuration.GetSection("Authentication:Linkedin");
        options.ClientId = linkedinAuthNSection["ClientId"];
        options.ClientSecret = linkedinAuthNSection["ClientSecret"];
        options.CallbackPath = new PathString("/signin-linkedin");
        options.AuthorizationEndpoint = "https://www.linkedin.com/oauth/v2/authorization";
        options.TokenEndpoint = "https://www.linkedin.com/oauth/v2/accessToken";
        options.UserInformationEndpoint = "https://api.linkedin.com/v2/userinfo";

        // 仅保留必要的登录权限
        options.Scope.Add("r_liteprofile");
        options.Scope.Add("r_emailaddress");

        // 映射LinkedIn用户字段到Claims
        options.ClaimActions.MapJsonKey(ClaimTypes.NameIdentifier, "sub");
        options.ClaimActions.MapJsonKey(ClaimTypes.Name, "name");
        options.ClaimActions.MapJsonKey(ClaimTypes.Email, "email");
        options.ClaimActions.MapJsonKey("urn:linkedin:picture", "picture");

        // 适配LinkedIn v2 API的请求规则
        options.Events = new OAuthEvents
        {
            OnCreatingTicket = async context =>
            {
                var request = new HttpRequestMessage(HttpMethod.Get, context.Options.UserInformationEndpoint);
                request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", context.AccessToken);

                var response = await context.Backchannel.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, context.HttpContext.RequestAborted);
                response.EnsureSuccessStatusCode();

                var user = JsonDocument.Parse(await response.Content.ReadAsStringAsync()).RootElement;
                context.RunClaimActions(user);
            }
        };
    });

最后测试前记得清理浏览器缓存,避免之前的错误授权状态干扰新的请求。

内容的提问来源于stack exchange,提问作者Zubair Rana

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.07 13:37:46