来自 GetAccessTokenForAppAsync 的 MsalClientException IDW10104

发布于 2025-01-16 12:26:44 字数 1375 浏览 1 评论 0原文

我有一个 ASP.NET Core Web API 在 Azure 中设置为应用程序服务,并在我们的 AzureAd 中进行应用程序注册 在

appsettings.json 中,我(匿名)

 "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "ourdomain.co.uk",
    "TenantId": "n9n999n9-9999-nnnn-9n9n9-9n9n9n9n9n9",
    "ClientId": "81933a15-157f-45b0-bc32-3d7d6d62f4a7",
    "Audience": "https://ourdomain.co.uk/breathe.notifications-service",
    "ClientSecret": "a6a6a6a~EEizqWNa8itAAAjcrycxnCtxaVgKTFx"
  },

该应用程序在 Azure Ad 中具有 API 权限,允许我调用另一个应用程序服务,审计。审核服务没有定义任何特定范围,但它确实有一个名为 Audit.Write 的应用程序角色。

在调用 API 中,我需要获取令牌来调用审核,因此我运行此代码,

var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

请注意对 GetAccessTokenForAppAsync 而不是更常见的 GetAccessTokenForUserAsync

我传递的范围字符串是

https://ourdomain.co.uk/us.audit-service/.default

当我调用 GetAccessTokenForAppAsync 时,它失败并出现 MSALException

IDW10104:客户端密钥和客户端证书都不能为空或 空白,并且只有一个必须包含在配置中 Web 应用程序调用 Web API 时。例如,在 appsettings.json 中 文件。

客户端密钥位于 AzureAd 配置中,我没有指定证书。

I have an ASP.NET Core Web API set up as App Service in Azure with an App Registration in our AzureAd

In appsettings.json I have (anonimized)

 "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "Domain": "ourdomain.co.uk",
    "TenantId": "n9n999n9-9999-nnnn-9n9n9-9n9n9n9n9n9",
    "ClientId": "81933a15-157f-45b0-bc32-3d7d6d62f4a7",
    "Audience": "https://ourdomain.co.uk/breathe.notifications-service",
    "ClientSecret": "a6a6a6a~EEizqWNa8itAAAjcrycxnCtxaVgKTFx"
  },

That app has an API permission in Azure Ad that allows me to call another app service, Audit. The audit service does not have any specific scopes defined but it does have an app role called Audit.Write

In the calling API i need to get a token to call audit so I run this code

var accessToken = await this.tokenAcquisition.GetAccessTokenForAppAsync(this.auditApiScope);
this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

Note the call to GetAccessTokenForAppAsync rather than the more common GetAccessTokenForUserAsync

The scope string that I am passing is

https://ourdomain.co.uk/us.audit-service/.default

When I call GetAccessTokenForAppAsync it is failing with MSALException

IDW10104: Both client secret and client certificate cannot be null or
whitespace, and only ONE must be included in the configuration of the
web app when calling a web API. For instance, in the appsettings.json
file.

The client secret is in the AzureAd config, I am not specifying a certificate.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

魔法唧唧 2025-01-23 12:26:44

我现在已经完成了这项工作,并且有两个选择,但在概述这些选项之前,我需要提供一些额外的背景知识。

此 Web Api 和我们创建的其他 Web Api 为 Azure Ad 用户和 Azure B2C 用户提供功能。此功能首先在 Microsoft.Identity.Web 1.11.0 中实现,自 1.11.0 发布以来我们一直在使用它。然而,我们总是遇到一个问题,即我们会生成数千个异常,因为 MSAL 对使用哪种方案感到困惑。

我们看到这篇博文,在 ASP.NET Core 3.1 中使用多个身份验证方案时删除误导性的 IDX10501 日志 这个 github 线程中有更多详细信息,https://github.com/oliviervaillancourt/blog/issues/3

我们的 Startup.cs 配置服务如下所示。

public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebApiAuthentication(this.configuration)
                                                            .EnableTokenAcquisitionToCallDownstreamApi()
                                                            .AddInMemoryTokenCaches();

    services.AddAuthentication()
            .AddMicrosoftIdentityWebApi(this.configuration, "AzureAdB2C", "B2CScheme", true);

    services.AddAuthentication("AzureAD_OR_AzureAdB2C")
                .AddMicrosoftIdentityWebApi(
                                            jwtBearerOptions =>
                                            {
                                                var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
                                                jwtBearerOptions.ForwardDefaultSelector = context =>
                                                {
                                                    var token = string.Empty;

                                                    if (context.Request.Headers.TryGetValue("Authorization", out var value))
                                                    {
                                                        string authorization = value;
                                                        if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                                                        {
                                                            token = authorization.Substring("Bearer ".Length).Trim();
                                                        }
                                                    }

                                                    if (token == null)
                                                    {
                                                        this.logger.LogInformation($"Cannot get the Token out of the Authorization header");
                                                    }

                                                    var jwtHandler = new JwtSecurityTokenHandler();

                                                    if (jwtHandler.CanReadToken(token))
                                                    {
                                                        var jwtToken = jwtHandler.ReadJwtToken(token);
                                                        var expectedB2CIssuer = $"{azureAdB2CConfig.GetValue<string>("Instance")}/{azureAdB2CConfig.GetValue<string>("TenantId")}/v2.0/";

                                                        if (string.Compare(jwtToken.Issuer, expectedB2CIssuer, true) == 0)
                                                        {
                                                            // Claim is from B2C so this request should be validated against the B2C scheme.
                                                            this.logger.LogInformation($"Request is with a B2C issued token so refer to B2CScheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
                                                            return "B2CScheme";
                                                        }
                                                        else
                                                        {
                                                            this.logger.LogInformation($"Request is not with a B2C issued token so refer to Bearer scheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
                                                        }
                                                    }
                                                    else
                                                    {
                                                        this.logger.LogInformation("Request token could not be read so refer to Bearer scheme");
                                                    }

                                                    return "Bearer";
                                                };
                                            },
                                            identityOptions =>
                                            {
                                                var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
                                                identityOptions.Instance = azureAdB2CConfig.GetValue<string>("Instance");
                                                identityOptions.TenantId = "AzureAD_OR_AzureAdB2C";
                                                identityOptions.ClientId = "AzureAD_OR_AzureAdB2C";
                                            },
                                            "AzureAD_OR_AzureAdB2C",
                                            false);

    services.AddControllers()
            .AddNewtonsoftJson();

    services.AddLogging(options =>
    {
        // hook the Console Log Provider
        options.AddConsole();
        options.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);

        // hook the Application Insights Provider
        options.AddFilter<ApplicationInsightsLoggerProvider>(string.Empty, Microsoft.Extensions.Logging.LogLevel.Trace);

        // pass the InstrumentationKey provided under the appsettings
        options.AddApplicationInsights(this.configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
    });
  
}

ForwardDefaultSelector 使用的逻辑帮助我们处理多个方案并将 ASP.NET 转发到正确的方案。

现在回到答案。

如果我删除 ForwardDefaultSelector,我将不再获得 IDW10104,但是我们用它来删除所有无关的异常方案,因此这实际上并不可行。

唯一可行的选择是将 Web Api 从最新版本的 Microsoft.Identity.Web 1.21.1 移至 1.16.0。导致我们遇到异常的问题是在 1.16.1 中引入的。我将在 MSAL github 上提出 1.16.1 的问题。我们之前使用的是 1.11.0。

I now have this working and have two options but before I outline those I need to offer some extra background.

This Web Api and others we have created offer functionality to Azure Ad users and Azure B2C users. This functionality was first possible with Microsoft.Identity.Web 1.11.0 and we hjave been using 1.11.0 since it was released. However we always had an issue where we would generate thousands of exceptions because MSAL was getting confused ny which scheme to use.

We came across this blog post, Removing misleading IDX10501 logs when using multiple authentication schemes in ASP.NET Core 3.1 there is more detail in this github thread, https://github.com/oliviervaillancourt/blog/issues/3.

Our Startup.cs Configure Services looks like this

public void ConfigureServices(IServiceCollection services)
{
    services.AddMicrosoftIdentityWebApiAuthentication(this.configuration)
                                                            .EnableTokenAcquisitionToCallDownstreamApi()
                                                            .AddInMemoryTokenCaches();

    services.AddAuthentication()
            .AddMicrosoftIdentityWebApi(this.configuration, "AzureAdB2C", "B2CScheme", true);

    services.AddAuthentication("AzureAD_OR_AzureAdB2C")
                .AddMicrosoftIdentityWebApi(
                                            jwtBearerOptions =>
                                            {
                                                var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
                                                jwtBearerOptions.ForwardDefaultSelector = context =>
                                                {
                                                    var token = string.Empty;

                                                    if (context.Request.Headers.TryGetValue("Authorization", out var value))
                                                    {
                                                        string authorization = value;
                                                        if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
                                                        {
                                                            token = authorization.Substring("Bearer ".Length).Trim();
                                                        }
                                                    }

                                                    if (token == null)
                                                    {
                                                        this.logger.LogInformation(
quot;Cannot get the Token out of the Authorization header");
                                                    }

                                                    var jwtHandler = new JwtSecurityTokenHandler();

                                                    if (jwtHandler.CanReadToken(token))
                                                    {
                                                        var jwtToken = jwtHandler.ReadJwtToken(token);
                                                        var expectedB2CIssuer = 
quot;{azureAdB2CConfig.GetValue<string>("Instance")}/{azureAdB2CConfig.GetValue<string>("TenantId")}/v2.0/";

                                                        if (string.Compare(jwtToken.Issuer, expectedB2CIssuer, true) == 0)
                                                        {
                                                            // Claim is from B2C so this request should be validated against the B2C scheme.
                                                            this.logger.LogInformation(
quot;Request is with a B2C issued token so refer to B2CScheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
                                                            return "B2CScheme";
                                                        }
                                                        else
                                                        {
                                                            this.logger.LogInformation(
quot;Request is not with a B2C issued token so refer to Bearer scheme. Token issuer: {jwtToken.Issuer} B2C Issuer: {expectedB2CIssuer}");
                                                        }
                                                    }
                                                    else
                                                    {
                                                        this.logger.LogInformation("Request token could not be read so refer to Bearer scheme");
                                                    }

                                                    return "Bearer";
                                                };
                                            },
                                            identityOptions =>
                                            {
                                                var azureAdB2CConfig = this.configuration.GetSection("AzureAdB2C");
                                                identityOptions.Instance = azureAdB2CConfig.GetValue<string>("Instance");
                                                identityOptions.TenantId = "AzureAD_OR_AzureAdB2C";
                                                identityOptions.ClientId = "AzureAD_OR_AzureAdB2C";
                                            },
                                            "AzureAD_OR_AzureAdB2C",
                                            false);

    services.AddControllers()
            .AddNewtonsoftJson();

    services.AddLogging(options =>
    {
        // hook the Console Log Provider
        options.AddConsole();
        options.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);

        // hook the Application Insights Provider
        options.AddFilter<ApplicationInsightsLoggerProvider>(string.Empty, Microsoft.Extensions.Logging.LogLevel.Trace);

        // pass the InstrumentationKey provided under the appsettings
        options.AddApplicationInsights(this.configuration["APPINSIGHTS_INSTRUMENTATIONKEY"]);
    });
  
}

The logic used by the ForwardDefaultSelector is what helps us work with multiple schemes and forward ASP.NET to the right scheme.

Now back to the answer.

If I remove the ForwardDefaultSelector I no longer get the IDW10104 however that is what we use to remopve all the extraneous exceptions schemes so that is not really going to be workable.

The only viable option is to move the Web Api from the latest version of Microsoft.Identity.Web 1.21.1 to 1.16.0. The issue that is causing us to get the exception was introduced in 1.16.1. I will raise an issue on the MSAL github for 1.16.1. We were previously using 1.11.0.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文