ID 令牌中缺少特定于 Azure AD v2.0 的可选声明

Azure AD v2.0-specific optional claims missing from ID Token

我正在尝试使用 Microsoft Identity Web - NuGet 添加可选声明,以便在 NET Core 3.1 WebApp 中进行用户身份验证。阅读 MS 文档,似乎唯一需要的步骤是在 Azure 的 App Registration Manifest 文件中声明可选声明。但是当使用两个不同的应用程序(我自己的代码和一个 MS 项目示例)测试登录过程时,当成功登录后从 Azure 返回时,可选声明似乎没有添加到 ID 令牌中,即它们根本不存在在调试中查看令牌详细信息时。

我不确定如何诊断这个问题以及在哪里跟踪问题,即我是否缺少 Azure 设置中的任何必需步骤?

旁注:只是为了确认它是我想要接收附加声明的 jwt ID 令牌,而不是用于调用图形或另一个 Web API 端点的 jwt 访问令牌。

MS 文档参考:v2.0-specific optional claims set

以下是清单文件的摘录:(请注意,我什至声明了“accessTokenAcceptedVersion”:2,因为我使用的可选声明在版本 1 中不可用,如果保留以上内容在默认 'null' 值下,Azure 将假设我们使用的是旧版本 1 - 一个可能的陷阱)

"accessTokenAcceptedVersion": 2,
"optionalClaims": {
    "idToken": [
        {
            "name": "given_name",
            "source": "user",
            "essential": false,
            "additionalProperties": []
        },
        {
            "name": "family_name",
            "source": "user",
            "essential": false,
            "additionalProperties": []
        }
    ],
    "accessToken": [],
    "saml2Token": []
},

从启动中提取 class:

public void ConfigureServices(IServiceCollection services)
    {
        // Added to original .net core template.
        // ASP.NET Core apps access the HttpContext through the IHttpContextAccessor interface and 
        // its default implementation HttpContextAccessor. It's only necessary to use IHttpContextAccessor 
        // when you need access to the HttpContext inside a service.
        // Example usage - we're using this to retrieve the details of the currrently logged in user in page model actions.
        services.AddHttpContextAccessor();

        // DO NOT DELETE (for now...)
        // This 'Microsoft.AspNetCore.Authentication.AzureAD.UI' library was originally used for Azure Ad authentication 
        // before we implemented the newer Microsoft.Identity.Web and Microsoft.Identity.Web.UI NuGet packages. 
        // Note after implememting the newer library for authetication, we had to modify the _LoginPartial.cshtml file.
        //services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
        //    .AddAzureAD(options => Configuration.Bind("AzureAd", options));

        ///////////////////////////////////

        // Add services required for using options.
        // e.g used for calling Graph Api from WebOptions class, from config file.
        services.AddOptions();

        // Add service for MS Graph API Service Client.
        services.AddTransient<OidcConnectEvents>();

        // Sign-in users with the Microsoft identity platform
        services.AddSignIn(Configuration);

        // Token acquisition service based on MSAL.NET
        // and chosen token cache implementation
        services.AddWebAppCallsProtectedWebApi(Configuration, new string[] { Constants.ScopeUserRead })
            .AddInMemoryTokenCaches();

        // Add the MS Graph SDK Client as a service for Dependancy Injection.
        services.AddGraphService(Configuration);

        ///////////////////////////////////

        // The following lines code instruct the asp.net core middleware to use the data in the "roles" claim in the Authorize attribute and User.IsInrole()
        // See https://docs.microsoft.com/aspnet/core/security/authorization/roles?view=aspnetcore-2.2 for more info.
        services.Configure<OpenIdConnectOptions>(OpenIdConnectDefaults.AuthenticationScheme, options =>
        {
            // The claim in the Jwt token where App roles are available.
            options.TokenValidationParameters.RoleClaimType = "roles";
        });

        // Adding authorization policies that enforce authorization using Azure AD roles. Polices defined in seperate classes.
        services.AddAuthorization(options =>
        {
            options.AddPolicy(AuthorizationPolicies.AssignmentToViewLogsRoleRequired, policy => policy.RequireRole(AppRole.ViewLogs));
        });

        ///////////////////////////////////

        services.AddRazorPages().AddMvcOptions(options =>
        {
            var policy = new AuthorizationPolicyBuilder()
                .RequireAuthenticatedUser()
                .Build();
            options.Filters.Add(new AuthorizeFilter(policy));
        }).AddMicrosoftIdentityUI();

        // Adds the service for creating the Jwt Token used for calling microservices.
        // Note we are using our independant bearer token issuer service here, NOT Azure AD
        services.AddScoped<JwtService>(); 
    }

示例 Razor PageModel 方法:

public void OnGet()
    {
        var username = HttpContext.User.Identity.Name;
        var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value;
        var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value;

        _logger.LogInformation("" + username + " requested the Index page");
    }

更新

越来越接近解决方案,但还没有完全解决。解决了几个问题:

  1. 我最初在 Azure 中创建租户是为了使用 B2C AD,尽管我不再使用 B2C 并已切换到 Azure AD。直到我删除了租户并创建了一个新租户,我才开始看到可选的声明正确地传递到 webapp。创建新租户并分配租户类型以使用 Azure AD 后,我发现 'Token Configuration' 菜单现在可用于通过 UI 配置可选声明,看来修改 App 清单是仍然需要,如上所示。

  1. 我必须将 'profile' 范围作为类型 'delegated' 添加到 Azure 中的 webapp API 权限。

最后一个未解决的问题是,虽然我可以在调试期间看到存在的声明,但我无法弄清楚如何检索声明值。

在下面的方法中,我可以在使用 Debug 时看到所需的声明,但无法弄清楚如何检索值:

public void OnGet()
    {
        var username = HttpContext.User.Identity.Name;

        var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "given_name")?.Value;
        var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == "family_name")?.Value;

        _logger.LogInformation("" + username + " requested the Index page");
    }

调试屏幕截图显示 given_name 和 family_name 存在:

我已经使用声明主体尝试了不同的代码示例来尝试获取值,但对我来说没有任何效果。希望这个最后的谜语对于知道所需语法的人来说相当简单,正如我们现在所说的那样,我们现在有必需的可选声明,只是不知道如何实际获取值。

非常感谢 'Dhivya G - MSFT Identity' 的帮助(请参阅我的原始问题下方的评论)下面的方法现在允许我从成功登录后从 Azure 返回的令牌 ID 访问所需的声明值。

    public void OnGet()
    {
        var username = HttpContext.User.Identity.Name;

        var forename = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.GivenName)?.Value;
        var surname = HttpContext.User.Claims.FirstOrDefault(c => c.Type == ClaimTypes.Surname)?.Value;

        _logger.LogInformation("" + username + " requested the Index page");
    }