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

Azure AD B2C登录时SignInManager.GetExternalLoginInfoAsync返回NULL问题

Azure AD B2C认证时SignInManager.GetExternalLoginInfoAsync()返回NULL的解决办法

问题背景

基于.NET 7的Web应用使用Microsoft.AspNetCore.Identity管理用户,通过Microsoft Identity Web和Microsoft Identity Web UI实现微软账号与Azure AD B2C双认证。微软账号认证正常,但Azure AD B2C认证时,SignInManager.GetExternalLoginInfoAsync()始终返回NULL,尝试设置IdentityConstants.ExternalScheme后问题仍存在。

现有配置

appsettings.json

{
    "DetailedErrors": true,
    "Logging": {
        "LogLevel": {
            "Default": "Error",
            "Microsoft.AspNetCore": "Error"
        }
    },
    "Microsoft": {
        "AppId": "<client-id>",
        "AppSecret": "<client-secret",
        "AuthEndPoint": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/authorize",
        "TokanEndPoint": "https://login.microsoftonline.com/<tenant-id>/oauth2/v2.0/token"
    },
    "B2C": {
        "Instance": "https://<domain-id>.b2clogin.com",
        "ClientId": "<client-id>",
        "Domain": "<domain-id>.onmicrosoft.com",
        "SignedOutCallbackPath": "/signout/<b2c_policy_id>",
        "SignUpSignInPolicyId": "<b2c_policy_id>"
    },
    "AllowedHosts": "*"
}

Program.cs

using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Portal.Interface.Web.Data;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
string connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options => options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>();

builder.Services.AddAuthentication()
    .AddMicrosoftAccount("Microsoft", "Microsoft", micorosoftOptions =>
    {
        micorosoftOptions.AuthorizationEndpoint = builder.Configuration.GetSection("Microsoft").GetValue<string>("AuthEndPoint");
        micorosoftOptions.TokenEndpoint = builder.Configuration.GetSection("Microsoft").GetValue<string>("TokanEndPoint");
        micorosoftOptions.ClientId = builder.Configuration.GetSection("Microsoft").GetValue<string>("AppId");
        micorosoftOptions.ClientSecret = builder.Configuration.GetSection("Microsoft").GetValue<string>("AppSecret");
        micorosoftOptions.AccessDeniedPath = "/Identity/Account/AccessDenied";
    })
    .AddMicrosoftIdentityWebApp(azureOptions =>
    {
        azureOptions.SignInScheme = IdentityConstants.ExternalScheme;
        azureOptions.Instance = builder.Configuration.GetSection("B2C").GetValue<string>("Instance");
        azureOptions.ClientId = builder.Configuration.GetSection("B2C").GetValue<string>("ClientId");
        azureOptions.Domain = builder.Configuration.GetSection("B2C").GetValue<string>("Domain");
        azureOptions.SignedOutCallbackPath = builder.Configuration.GetSection("B2C").GetValue<string>("SignedOutCallbackPath");
        azureOptions.SignUpSignInPolicyId = builder.Configuration.GetSection("B2C").GetValue<string>("SignUpSignInPolicyId");
    });

builder.Services.AddRazorPages();
builder.Services.AddAntiforgery(a => a.HeaderName = "XSRF-TOKEN");

WebApplication app = builder.Build();

if (app.Environment.IsDevelopment())
    app.UseMigrationsEndPoint();
else
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

回调端点代码

public async Task<IActionResult> OnGetCallbackAsync(string returnUrl = null, string remoteError = null)
{
    returnUrl = returnUrl ?? Url.Content("~/~");
    if (remoteError != null)
    {
        ErrorMessage = $"Error from external provider: {remoteError}";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }
    var info = await _signInManager.GetExternalLoginInfoAsync(); //Returns null.
    if (info == null)
    {
        ErrorMessage = "Error loading external login information.";
        return RedirectToPage("./Login", new { ReturnUrl = returnUrl });
    }
    //Code omitted...
}

解决措施

1. 补全认证中间件并调整顺序

现有代码缺少app.UseAuthentication()中间件,这是触发认证流程的关键,必须放在UseAuthorization之前:

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// 新增该行,确保认证中间件优先执行
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();

2. 统一认证方案配置

显式指定默认认证方案,同时确保两个外部提供者都使用IdentityConstants.ExternalScheme作为登录方案,避免冲突:

builder.Services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = false)
    .AddEntityFrameworkStores<ApplicationDbContext>();

// 显式设置默认认证方案
builder.Services.AddAuthentication(options =>
{
    options.DefaultAuthenticateScheme = IdentityConstants.ApplicationScheme;
    options.DefaultSignInScheme = IdentityConstants.ApplicationScheme;
    options.DefaultChallengeScheme = IdentityConstants.ApplicationScheme;
})
.AddMicrosoftAccount("Microsoft", "Microsoft", micorosoftOptions =>
{
    micorosoftOptions.AuthorizationEndpoint = builder.Configuration.GetSection("Microsoft").GetValue<string>("AuthEndPoint");
    micorosoftOptions.TokenEndpoint = builder.Configuration.GetSection("Microsoft").GetValue<string>("TokanEndPoint");
    micorosoftOptions.ClientId = builder.Configuration.GetSection("Microsoft").GetValue<string>("AppId");
    micorosoftOptions.ClientSecret = builder.Configuration.GetSection("Microsoft").GetValue<string>("AppSecret");
    micorosoftOptions.AccessDeniedPath = "/Identity/Account/AccessDenied";
    // 给微软账号认证添加外部登录方案配置
    micorosoftOptions.SignInScheme = IdentityConstants.ExternalScheme;
})
.AddMicrosoftIdentityWebApp(azureOptions =>
{
    // 使用配置绑定简化代码,减少手动取值错误
    builder.Configuration.Bind("B2C", azureOptions);
    azureOptions.SignInScheme = IdentityConstants.ExternalScheme;
});

3. 验证Azure AD B2C门户配置

  • 检查应用注册的回复URL是否与应用默认回调路径(/signin-oidc)一致,若自定义回调路径需同步更新
  • 确认注销URL与SignedOutCallbackPath配置匹配

4. 开启调试日志排查细节

调整日志级别为Debug,查看认证流程中的详细错误:

"Logging": {
    "LogLevel": {
        "Default": "Debug",
        "Microsoft.AspNetCore.Authentication": "Debug",
        "Microsoft.Identity.Web": "Debug"
    }
}

通过日志可查看外部认证是否成功获取令牌、Cookie生成与存储过程是否存在异常。

内容的提问来源于stack exchange,提问作者Razack Mahomed

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 17:17:05