如何使用 IConfidentialClientApplication 获取安全 Web API 的令牌

Posted

技术标签:

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

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

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

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

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

    我不太确定我是否缺少此 C# 代码中的步骤与场景 1 中的步骤。 或者,如果我需要在 Azure 中为已在 Azure AD 中注册的 Web api 配置任何特殊访问/权限。 我应该尝试在 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) 与在前端应用中获得的令牌时,会有一些不同的属性。

【问题讨论】:

所以您试图代表用户从后端调用 api? docs.microsoft.com/en-us/azure/active-directory/develop/… 阅读这篇文章,我会说这就是我正在寻找的用例。感谢分享 【参考方案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

【讨论】:

以上是关于如何使用 IConfidentialClientApplication 获取安全 Web API 的令牌的主要内容,如果未能解决你的问题,请参考以下文章

如何使用本机反应创建登录以及如何验证会话

如何在自动布局中使用约束标识符以及如何使用标识符更改约束? [迅速]

如何使用 AngularJS 的 ng-model 创建一个数组以及如何使用 jquery 提交?

如何使用laravel保存所有行数据每个行名或相等

如何使用 Math.Net 连接矩阵。如何使用 Math.Net 调用特定的行或列?

WSARecv 如何使用 lpOverlapped?如何手动发出事件信号?