从 React SPA/Azure 静态 Web 应用调用 Azure AD 安全 Azure 函数
Calling an Azure AD secured Azure Function from React SPA/Azure Static Web App
我正在使用作为 Azure 静态 Web 应用程序托管的 React 开发 SPA。该应用程序使用 Azure AD 身份验证进行保护,效果很好,我已经构建了一个运行良好的登录名,我可以使用我获得的令牌调用 Azure (Graph) APIs 并检索授予范围的信息(例如用户配置文件)图片)。为此,我使用了一个名为 React AAD MSAL 的包装器,它巧妙地包装了 Microsoft 身份验证库 (msal@1.4.0)。
到目前为止一切顺利,这里没有问题。但我当然需要一个后端。我决定使用 Azure Functions 来实现这一点,因为无服务器是这里对我来说最好的方式。所以我制作了一个快速的 HTTP 触发器原型,它在 Azure 中作为 Azure Function 运行,当我使用正确的参数调用 URL 时可以正常工作。
但是Azure Function当然需要保护,所以只有我的React App可以调用这个函数。所以我认为应该有办法通过 Azure AD 执行此操作,因为我的用户已经这样登录了。
我试了又试我在网上找到的不同方法,但 none 似乎有效,或者我做错了什么。
我尝试遵循的一般教程是来自 MS 本身的 this one。我尝试使用“快速”设置,但当然没有用。我试过高级配置,也没用。高级教程说你需要为该服务注册一个应用程序,我什至不确定这是我的静态 Web 应用程序还是一个新的应用程序(我都试过了,但都没有成功)。是否足以告诉 Azure 函数它现在受 AAD 保护并且只能接受来自受访问令牌保护的源的调用,该访问令牌包含我的应用程序的应用程序 ID(在设置中提供)?您可以轻松地提供所有这些设置,它似乎不起作用。
所以我很早就在这里拖延了。要调用函数本身,我首先需要获得一个授权令牌。根据 this tutorial from MS(请参阅“验证来自提供商的令牌”),我需要将登录我的 SPA Web 应用程序时获得的访问令牌发送到以 .auth/login/aad
结尾的 Azure Function 端点。获取此令牌很容易,因为 React AAD MSAL 提供了一种方法 authProvider.getAccessToken()
,我可以使用它来提取它。然后我向 https://<My Azure Function URI>/.auth/login/aad
发出 POST 请求,正文中的访问令牌为 JSON { 'access_token': authToken.accessToken }
。我应该得到一个身份验证令牌,然后我可以用它来调用实际函数,但无论我尝试什么,我总是得到相同的响应:You do not have permission to view this directory or page.
这就是我所在的地方。我尝试了不同的方法和解决方案,但都无济于事。也许我从头开始就做错了,也许我使用了错误的方法,我现在真的不知道。有人对此有经验吗?我的一般方法有问题吗,我需要做其他事情吗?还是我需要更改配置中的某些内容?
编辑
既然有人问了,下面是我检索令牌的方法。这背后的概念是使用 redux-thunk
将异步操作分派到 react-redux 存储。我不仅为这里的这个问题而且为我的测试简化了它。现在我只是想获取身份验证令牌并记录 POST 请求给我的答案:
import { authProvider } from '../../Authentication/AuthProvider';
//Fetch
async function getAccessToken(authToken) {
const body = { 'access_token': authToken.accessToken };
fetch('https://<My Azure function URL>/.auth/login/aad', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
},
).then(response => {
console.log(response);
});
}
export const fetchAddressData = () => async dispatch => {
const token = await authProvider.getAccessToken();
await getAccessToken(token);
// The actual call to the Azure function will go here once we have an Authentication Token
}
authProvider
是来自 react-aad msal
的组件,配置如下所示:
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
//MSAL Config
const config = {
auth: {
authority: '<Tenant ID>',
clientId: '<Client ID from App registration (Azure Static Web App)>',
redirectUri: window.location.origin
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
'openid',
'user.read',
'https://<Azure Function URI>/user_impersonation'
],
forceRefresh: true
}
// Options
const options = {
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)
编辑 2
我调整了一些额外的设置来尝试使用用户模拟,但仍然没有成功。这是对我当前的 Azure 设置的概述,这些设置对此很重要(我忘记了吗?)。
Azure 函数:
身份验证已激活,仅 AAD 身份验证,高级设置:
Azure 函数 - 应用程序注册:
身份验证设置:
客户端密码:
公开 API - 公开 user_impersonation API 以便 Web 应用程序可以使用它:
Azure 静态 Web 应用程序 (React SPA) - 应用程序注册:
在 Azure 函数中用作令牌受众的应用程序 URI ID(高级身份验证设置):
API 权限 - 使用由 Azure Function App 注册公开的 user_impersonation API:
这个配置有什么问题吗?它很可能是,但我不知道是什么,因为我遵循了 MSDN 上的教程。我后来才添加了 user_impersonation,因为它不起作用。
根据提供的信息,您没有在 authProvider
文件中配置正确的范围。您需要在创建 AD 应用程序时添加您定义的范围以保护功能。所以请将范围更新为 scopes: ["openid","<your function app scope>"]
in authProvider
.
例如
创建Azure AD应用保护功能
注册 Azure AD 应用程序。完成后,请复制 应用程序(客户端)ID 和 目录(租户)ID
配置重定向 URI。 Select Web 并输入 <app-url>/.auth/login/aad/callback
.
启用隐式授权流程
定义API范围并复制
创建客户端密钥。
在应用服务应用程序中启用 Azure Active Directory
创建客户端AD应用访问功能
- 注册申请
- 启用隐式授权流程
- 配置API权限。让您的客户端应用程序具有访问功能的权限
代码
authProvider
import { MsalAuthProvider, LoginType } from "react-aad-msal";
import { Logger, LogLevel } from "msal";
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: "https://login.microsoftonline.com/<tenant>",
clientId: "<>",
postLogoutRedirectUri: window.location.origin,
redirectUri: window.location.origin,
validateAuthority: true,
navigateToLoginRequestUrl: false,
},
system: {
logger: new Logger(
(logLevel, message, containsPii) => {
console.log("[MSAL]", message);
},
{
level: LogLevel.Verbose,
piiLoggingEnabled: false,
}
),
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
},
},
{
scopes: [
"openid",
"<the scope you define for your function>",
],
forceRefresh: true,
},
{
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + "/auth.html",
}
);
- 致电API
const CallAPI= async () => {
// You should should use getAccessToken() to fetch a fresh token before making API calls
const authToken = await provider.getAccessToken();
console.log(authToken.accessToken);
let body = { access_token: authToken.accessToken };
let res = await fetch(
"<your function url>/.auth/login/aad",
{
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
let data = await res.json();
console.log(data);
body = { name: "Azure" };
res = await fetch("<>", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-ZUMO-AUTH": data["authenticationToken"],
},
body: JSON.stringify(body),
});
data = await res.text();
console.log(data);
};
我有一段时间一直在处理同样的问题。如果您确定获得了正确的访问令牌并正确传递了它,请查看门户中的配置。如果您为函数应用程序自动创建了应用程序注册,请检查 ISSUER URL 的设置方式。您可以在函数 app>authentication>edit 中找到它。确保 url 末尾没有 /v2.0。 Azure 函数仅适用于默认 (/v1.0) 路由。
我正在使用作为 Azure 静态 Web 应用程序托管的 React 开发 SPA。该应用程序使用 Azure AD 身份验证进行保护,效果很好,我已经构建了一个运行良好的登录名,我可以使用我获得的令牌调用 Azure (Graph) APIs 并检索授予范围的信息(例如用户配置文件)图片)。为此,我使用了一个名为 React AAD MSAL 的包装器,它巧妙地包装了 Microsoft 身份验证库 (msal@1.4.0)。
到目前为止一切顺利,这里没有问题。但我当然需要一个后端。我决定使用 Azure Functions 来实现这一点,因为无服务器是这里对我来说最好的方式。所以我制作了一个快速的 HTTP 触发器原型,它在 Azure 中作为 Azure Function 运行,当我使用正确的参数调用 URL 时可以正常工作。
但是Azure Function当然需要保护,所以只有我的React App可以调用这个函数。所以我认为应该有办法通过 Azure AD 执行此操作,因为我的用户已经这样登录了。
我试了又试我在网上找到的不同方法,但 none 似乎有效,或者我做错了什么。
我尝试遵循的一般教程是来自 MS 本身的 this one。我尝试使用“快速”设置,但当然没有用。我试过高级配置,也没用。高级教程说你需要为该服务注册一个应用程序,我什至不确定这是我的静态 Web 应用程序还是一个新的应用程序(我都试过了,但都没有成功)。是否足以告诉 Azure 函数它现在受 AAD 保护并且只能接受来自受访问令牌保护的源的调用,该访问令牌包含我的应用程序的应用程序 ID(在设置中提供)?您可以轻松地提供所有这些设置,它似乎不起作用。
所以我很早就在这里拖延了。要调用函数本身,我首先需要获得一个授权令牌。根据 this tutorial from MS(请参阅“验证来自提供商的令牌”),我需要将登录我的 SPA Web 应用程序时获得的访问令牌发送到以 .auth/login/aad
结尾的 Azure Function 端点。获取此令牌很容易,因为 React AAD MSAL 提供了一种方法 authProvider.getAccessToken()
,我可以使用它来提取它。然后我向 https://<My Azure Function URI>/.auth/login/aad
发出 POST 请求,正文中的访问令牌为 JSON { 'access_token': authToken.accessToken }
。我应该得到一个身份验证令牌,然后我可以用它来调用实际函数,但无论我尝试什么,我总是得到相同的响应:You do not have permission to view this directory or page.
这就是我所在的地方。我尝试了不同的方法和解决方案,但都无济于事。也许我从头开始就做错了,也许我使用了错误的方法,我现在真的不知道。有人对此有经验吗?我的一般方法有问题吗,我需要做其他事情吗?还是我需要更改配置中的某些内容?
编辑
既然有人问了,下面是我检索令牌的方法。这背后的概念是使用 redux-thunk
将异步操作分派到 react-redux 存储。我不仅为这里的这个问题而且为我的测试简化了它。现在我只是想获取身份验证令牌并记录 POST 请求给我的答案:
import { authProvider } from '../../Authentication/AuthProvider';
//Fetch
async function getAccessToken(authToken) {
const body = { 'access_token': authToken.accessToken };
fetch('https://<My Azure function URL>/.auth/login/aad', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(body)
},
).then(response => {
console.log(response);
});
}
export const fetchAddressData = () => async dispatch => {
const token = await authProvider.getAccessToken();
await getAccessToken(token);
// The actual call to the Azure function will go here once we have an Authentication Token
}
authProvider
是来自 react-aad msal
的组件,配置如下所示:
import { MsalAuthProvider, LoginType } from 'react-aad-msal';
//MSAL Config
const config = {
auth: {
authority: '<Tenant ID>',
clientId: '<Client ID from App registration (Azure Static Web App)>',
redirectUri: window.location.origin
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: true
}
};
// Authentication Parameters
const authenticationParameters = {
scopes: [
'openid',
'user.read',
'https://<Azure Function URI>/user_impersonation'
],
forceRefresh: true
}
// Options
const options = {
loginType: LoginType.Redirect,
tokenRefreshUri: window.location.origin
}
export const authProvider = new MsalAuthProvider(config, authenticationParameters, options)
编辑 2
我调整了一些额外的设置来尝试使用用户模拟,但仍然没有成功。这是对我当前的 Azure 设置的概述,这些设置对此很重要(我忘记了吗?)。
Azure 函数:
身份验证已激活,仅 AAD 身份验证,高级设置:
Azure 函数 - 应用程序注册:
身份验证设置:
客户端密码:
公开 API - 公开 user_impersonation API 以便 Web 应用程序可以使用它:
Azure 静态 Web 应用程序 (React SPA) - 应用程序注册:
在 Azure 函数中用作令牌受众的应用程序 URI ID(高级身份验证设置):
API 权限 - 使用由 Azure Function App 注册公开的 user_impersonation API:
这个配置有什么问题吗?它很可能是,但我不知道是什么,因为我遵循了 MSDN 上的教程。我后来才添加了 user_impersonation,因为它不起作用。
根据提供的信息,您没有在 authProvider
文件中配置正确的范围。您需要在创建 AD 应用程序时添加您定义的范围以保护功能。所以请将范围更新为 scopes: ["openid","<your function app scope>"]
in authProvider
.
例如
创建Azure AD应用保护功能
注册 Azure AD 应用程序。完成后,请复制 应用程序(客户端)ID 和 目录(租户)ID
配置重定向 URI。 Select Web 并输入
<app-url>/.auth/login/aad/callback
.启用隐式授权流程
定义API范围并复制
创建客户端密钥。
在应用服务应用程序中启用 Azure Active Directory
创建客户端AD应用访问功能
- 注册申请
- 启用隐式授权流程
- 配置API权限。让您的客户端应用程序具有访问功能的权限
代码
authProvider
import { MsalAuthProvider, LoginType } from "react-aad-msal";
import { Logger, LogLevel } from "msal";
export const authProvider = new MsalAuthProvider(
{
auth: {
authority: "https://login.microsoftonline.com/<tenant>",
clientId: "<>",
postLogoutRedirectUri: window.location.origin,
redirectUri: window.location.origin,
validateAuthority: true,
navigateToLoginRequestUrl: false,
},
system: {
logger: new Logger(
(logLevel, message, containsPii) => {
console.log("[MSAL]", message);
},
{
level: LogLevel.Verbose,
piiLoggingEnabled: false,
}
),
},
cache: {
cacheLocation: "sessionStorage",
storeAuthStateInCookie: true,
},
},
{
scopes: [
"openid",
"<the scope you define for your function>",
],
forceRefresh: true,
},
{
loginType: LoginType.Popup,
tokenRefreshUri: window.location.origin + "/auth.html",
}
);
- 致电API
const CallAPI= async () => {
// You should should use getAccessToken() to fetch a fresh token before making API calls
const authToken = await provider.getAccessToken();
console.log(authToken.accessToken);
let body = { access_token: authToken.accessToken };
let res = await fetch(
"<your function url>/.auth/login/aad",
{
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(body),
}
);
let data = await res.json();
console.log(data);
body = { name: "Azure" };
res = await fetch("<>", {
method: "POST",
mode: "cors",
headers: {
"Content-Type": "application/json",
"X-ZUMO-AUTH": data["authenticationToken"],
},
body: JSON.stringify(body),
});
data = await res.text();
console.log(data);
};
我有一段时间一直在处理同样的问题。如果您确定获得了正确的访问令牌并正确传递了它,请查看门户中的配置。如果您为函数应用程序自动创建了应用程序注册,请检查 ISSUER URL 的设置方式。您可以在函数 app>authentication>edit 中找到它。确保 url 末尾没有 /v2.0。 Azure 函数仅适用于默认 (/v1.0) 路由。