Azure Functions 应用程序 + Auth0 提供程序,使用身份验证令牌调用 API 时出现 401

Posted

技术标签:

【中文标题】Azure Functions 应用程序 + Auth0 提供程序,使用身份验证令牌调用 API 时出现 401【英文标题】:Azure Functions app + Auth0 provider, getting 401 when calling API with auth token 【发布时间】:2022-01-12 20:53:49 【问题描述】:

我已成功阅读并实施本地开发项目以匹配 Auth0 的 Complete Guide To React User Authentication with Auth0。我对实施充满信心,因为登录和路由保护的所有方面都正常工作,并且本地快速服务器成功地验证了使用通过 Auth0 React SDK 生成的身份验证令牌的 API 调用。

我在示例项目的 external-apis.js 视图中添加了第三个按钮,用于调用我尝试与之集成的另一个 API,它是一个 Azure Functions 应用程序。我想以与快速服务器相同的方式使用此 API 的 Auth0,并利用 Azure 的“Easy Auth”功能,如 in this MS doc 所讨论的那样。我在 Azure Function 应用程序 per this MS doc 中实现了一个 OpenID Connect 提供程序,它指向我的 Auth0 应用程序。

调用此 Azure Function 应用 API 的函数如下所示:

  const callAzureApi = async () => 
    try 
      const token = await getAccessTokenSilently();
      await fetch(
        'https://example.azurewebsites.net/api/ExampleEndPoint',
        
          method: 'GET',
          headers: 
            'content-type': 'application/json',
            authorization: `Bearer $token`,
          ,
        
      )
        .then((response) => response.json())
        .then((response) => 
          setMessage(JSON.stringify(response));
        )
        .catch((error) => 
          setMessage(error.message);
        );
     catch (error) 
      setMessage(error.message);
    
  ;

我的问题是调用此 Azure 函数应用 API 始终返回 401(未授权)响应,即使正在发送授权令牌。如果我将 Azure 门户中的授权设置更改为不需要身份验证,则代码会正确检索数据,因此我确信代码是正确的。

但是,为了将 Auth0 用作 Azure 后端的身份验证提供程序,我在设置中是否遗漏了一些其他内容?

【问题讨论】:

【参考方案1】:

通过继续阅读文档和博客,我能够确定我最初的实现中缺少什么。简而言之,至少在使用像 Auth0 这样的 OpenID Connect 提供程序时,在阅读了有关 Azure 的 tge“Easy Auth”功能后,我的期望有点高。具体来说,JSON Web Token (JWT) 的验证并不是免费的,需要进一步实施。

我的应用正在使用 React Auth0 SDK 将用户登录到身份提供程序并获取授权令牌以发送其 API 请求。 client-directed sign-in flow 的 Azure 文档讨论了使用特定 POST 调用验证 JWT 的能力,该调用使用标头中的 JWT 对 auth 端点进行验证,但鉴于 OpenID Connect 未在提供程序中列出,即使此功能在这里似乎也无法实现列表,无论如何我尝试它的尝试仍然只产生 401。

因此,答案是将 JWT 验证直接实现到 Azure 函数本身,并且仅当请求标头中的 JWT 可以验证时才返回正确的响应。我想感谢 Boris Wilhelm 和 Ben Chartrand 的博客文章,他们帮助我最终了解如何正确使用 Auth0 作为 Azure Functions 后端 API。

我创建了以下 Security 对象来执行令牌验证。 ConfigurationManager 的静态特性对于缓存配置以减少对提供程序的 HTTP 请求非常重要。 (我的 Azure Functions 项目是用 C# 编写的,而不是 React JS 前端应用程序。)

using System;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.IdentityModel.Protocols;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using Microsoft.IdentityModel.Tokens;

namespace ExampleProject.Common 
    public static class Security 
        private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager;
        private static readonly string ISSUER = Environment.GetEnvironmentVariable("Auth0Url", EnvironmentVariableTarget.Process);
        private static readonly string AUDIENCE = Environment.GetEnvironmentVariable("Auth0Audience", EnvironmentVariableTarget.Process);

        static Security()
        
            var documentRetriever = new HttpDocumentRetriever RequireHttps = ISSUER.StartsWith("https://");

            _configurationManager = new ConfigurationManager<OpenIdConnectConfiguration> (
                $"ISSUER.well-known/openid-configuration",
                new OpenIdConnectConfigurationRetriever(),
                documentRetriever
            );
        

        public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) 
            if(value?.Scheme != "Bearer")
                return null;

            var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None);

            var validationParameter = new TokenValidationParameters 
                RequireSignedTokens = true,
                ValidAudience = AUDIENCE,
                ValidateAudience = true,
                ValidIssuer = ISSUER,
                ValidateIssuer = true,
                ValidateIssuerSigningKey = true,
                ValidateLifetime = true,
                IssuerSigningKeys = config.SigningKeys
            ;

            ClaimsPrincipal result = null;
            var tries = 0;

            while (result == null && tries <= 1) 
                try 
                    var handler = new JwtSecurityTokenHandler();
                    result = handler.ValidateToken(value.Parameter, validationParameter, out var token);
                 catch (SecurityTokenSignatureKeyNotFoundException) 
                    // This exception is thrown if the signature key of the JWT could not be found.
                    // This could be the case when the issuer changed its signing keys, so we trigger
                    // a refresh and retry validation.
                    _configurationManager.RequestRefresh();
                    tries++;
                 catch (SecurityTokenException) 
                    return null;
                
            

            return result;
        
    

然后,在运行任何其他代码来处理请求之前,我在任何 HTTP 触发函数的顶部添加了一小段样板代码:

ClaimsPrincipal principal;
if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) 
    return new UnauthorizedResult();

有了这个,我终于有了我正在寻找的实现。我想用更通用的东西来改进实现,比如自定义属性,但我不确定这对于 OpenID Connect 提供程序是否可行。尽管如此,这对我来说是一个完全可以接受的解决方案,并且为我提供了在使用 React 前端和 Azure Functions 后端时所寻求的安全级别。

干杯!

【讨论】:

看看这个post。您所需要的只是一个包,您可以将您的 Functions 应用程序配置为像 WebAPI 一样使用在函数或类上提供的 [FunctionAuthorize] 属性。

以上是关于Azure Functions 应用程序 + Auth0 提供程序,使用身份验证令牌调用 API 时出现 401的主要内容,如果未能解决你的问题,请参考以下文章

添加 Azure 存储 Blob 容器输入绑定 Azure Functions Java

从 Azure Functions 访问 Azure Key Vault 时访问被拒绝

IHostedService 可用于 Azure Functions 应用程序吗?

Azure Functions 突然找不到入口点错误

Azure Functions 的 Azure 队列触发器:配置最小轮询间隔

Azure Functions 中的 DI