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 应用程序吗?