【问题标题】:How to get a token for a secure web API using IConfidentialClientApplication如何使用 IConfidentialClientApplication 获取安全 Web API 的令牌
【发布时间】:2021-10-20 17:08:28
【问题描述】:

我会尽量简短地解释它。我的问题?尝试从 C# 代码(控制台应用程序)获取令牌时,我得到一个无效令牌。

场景 1(来自前端应用程序):

  1. 用户在 React 应用中输入他/她的凭据。
  2. 调用https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize端点。
  3. 已检索到令牌 ID(到目前为止一切顺利)
  4. 当经过身份验证的用户向私有 Web API 的任何端点发出请求时,会再次调用以下端点:https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token,并附加步骤 3 中的令牌 ID。
  5. 检索到新的 JWT 令牌
  6. 此 JWT 令牌附加到向 Web API 发出的请求的标头中
  7. 响应返回到 React 应用程序。

这是我们在 React 应用程序和 Web API 之间进行交互时使用的常规流程。这是来自 MS docs

的可视化流程

现在,出现了一个新场景:

场景 2(尝试从 c# 代码执行/模拟相同的操作):

我正在尝试从 C# 代码将 JWT 令牌附加到标头,为此我正在使用 MSAL.NET。官方doc

对于我的测试,我使用的是控制台应用程序:

private static async Task RunAsync()
    {
        string clientId = "client Id of the application that I have registered using azure app registration in Azure B2C";
        string clientSecret = "client secret of the application that I have registered using azure app registration in Azure B2C";
        string instance = "https://login.microsoftonline.com/{0}/";
        string tenantId = "Tenant Id that I can see when I open the application that I have registered using azure app registration in Azure B2C";
        string webAppUri = "web app domain";

        // For Web applications that use OpenID Connect Authorization Code flow, use IConfidentialClientApplication
        IConfidentialClientApplication app;

        app = ConfidentialClientApplicationBuilder
                .Create(clientId)
                .WithClientSecret(clientSecret)
                .WithAuthority(new Uri($"https://login.microsoftonline.com/{tenantId}"))
                .WithLegacyCacheCompatibility(false)
                .Build();

        // For confidential clients, this value should use a format similar to {Application ID URI}/.default.
        // https://docs.microsoft.com/en-us/azure/active-directory/develop/quickstart-v2-netcore-daemon#requesting-tokens
        string[] scopes = new string[] { $"{webAppUri}/.default" };

        AuthenticationResult result = null;

        try
        {
            result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("Token acquired");
            Console.WriteLine($"{result.AccessToken}");

            Console.ResetColor();
        }
        catch (MsalServiceException ex) when (ex.Message.Contains("AADSTS70011"))
        {
            // Invalid scope. The scope has to be of the form "https://resourceurl/.default"
            // Mitigation: change the scope to be as expected
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Scope provided is not supported");
            Console.ResetColor();
        }
        

        if (result != null)
        {
            var httpClient = new HttpClient();
            var apiCaller = new ProtectedApiCallHelper(httpClient);

            string webApiUrl = "http://localhost:12345/mycustomwebapi/list";

            var defaultRequetHeaders = httpClient.DefaultRequestHeaders;
            if (defaultRequetHeaders.Accept == null || !defaultRequetHeaders.Accept.Any(m => m.MediaType == "application/json"))
            {
                httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            }
            defaultRequetHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);

            HttpResponseMessage response = await httpClient.GetAsync(webApiUrl);
            if (response.IsSuccessStatusCode)
            {
                string json = await response.Content.ReadAsStringAsync();
                var jsonResult = JsonConvert.DeserializeObject<List<JObject>>(json);
            }
            else
            {
                Console.WriteLine($"Failed to call the Web Api: {response.StatusCode}");
                string content = await response.Content.ReadAsStringAsync();
            }
            Console.ResetColor();
        }
    }

上述代码的问题是我得到了一个格式正确的 JWT 令牌并将其附加到标头。但是在调用自定义/安全网络 API 时,我收到 401 Unauthorized response

所以,我对此有不同的想法:

  1. 我不太确定我是否缺少此 C# 代码中的步骤与场景 1 中的步骤。
  2. 或者,如果我需要在 Azure 中为已在 Azure AD 中注册的 Web api 配置任何特殊访问/权限。
  3. 我应该尝试在 C# 中执行相同的步骤吗?首先调用:https://login.microsoftonline.com/{tenant}/oauth2/v2.0/authorize 端点和https://login.microsoftonline.com/{tenant}/oauth2/v2.0/token 以获取有效的 JWT 令牌?

另外一件事是,当比较来自 C# 的 JWT 令牌 (https://jwt.io) 与在前端应用中获得的令牌时,会有一些不同的属性。

【问题讨论】:

标签: c# azure .net-core azure-active-directory


【解决方案1】:

获取机密客户端应用程序的令牌用于当前应用程序是中间层服务的情况,该服务使用代表最终用户的令牌调用。应用程序可以使用令牌oboAssertion 代表该用户请求另一个令牌来访问下游Web API。请参考 thomas 建议的 OAuth2.0 On-Behalf-Of flow | Microsoft Docs-谢谢@Thomas

对于机密客户请求的范围,使用类似于 {Application ID URI}/.default 的格式。

api://{api app client id}/.default

对于自定义 Web API,{Application ID URI} 在 Azure 门户中定义,位于 Application Registration (Preview) > Expose an API

大多数情况下,401 错误意味着您的令牌的受众与您的 api 不匹配。因此,当您请求令牌时,必须确保将范围设置为您的 api。检查 aud 声明,并确保这是否是您想要的 api。 当你暴露一个受 Azure 保护的 api 时,你需要将作用域设置为你的自定义 api,通常是 api://{api app client id}/scope 名称,然后你需要将客户端应用程序添加到 api 应用程序中。

其他参考: reference1 , reference2

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-12-19
    • 2015-09-19
    • 2016-06-09
    • 2019-04-02
    • 2013-02-14
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多