我正在使用React开发一个以Azure静态Web应用程序托管的SPA。该应用程序通过运行良好的Azure AD身份验证进行了安全保护,我已经构建了一个可以正常运行的登录名,并且可以使用获得的令牌调用Azure(Graph)API并检索授予的范围的信息(例如用户个人资料图片)。为此,我使用了一个名为React AAD MSAL的包装器,该包装器巧妙地包装了Microsoft身份验证库(msal@1.4.0)。
到目前为止很好,这里没有问题。但是,我当然需要一个后端。我决定使用Azure Functions来做到这一点,因为无服务器是我的最佳选择。因此,我制作了一个快速的HTTP触发器原型,当我使用正确的参数调用URL时,Azure Function und可以在Azure中运行。
但是当然需要保护Azure函数,因此只有我的React App可以调用此函数。因此,我认为应该有一种方法可以通过Azure AD进行此操作,因为我的用户已经以这种方式登录。
我尝试了各种尝试,也尝试了各种不同的在线查找方法,但是这些方法似乎都不起作用,或者我做错了事。
我尝试遵循的一般教程是来自MS本身的this one。我尝试使用“ Express”设置,但这当然不起作用。我尝试了高级配置,但也没有用。高级教程说您需要为该服务注册一个应用程序,我什至不确定这可以是我的静态Web应用程序还是新的(我尝试都没有成功)。仅仅告诉Azure函数它现在已经受到AAD保护,并且仅接受来自包含设置中提供的我的应用程序的应用程序ID的访问令牌保护的源的调用,这还不够吗?您可以轻松提供所有这些设置,但这似乎不起作用。
所以我在这里很早就拖延了。要调用该函数本身,我首先需要获取一个授权令牌。根据{{3}}(请参阅“验证来自提供程序的令牌”),我需要将登录SPA Web应用程序时获得的访问令牌发送到以.auth/login/aad
结尾的Azure Function终结点。获得此令牌很容易,因为React AAD MSAL提供了一种方法authProvider.getAccessToken()
,我可以用它来提取它。然后,我以JSON https://<My Azure Function URI>/.auth/login/aad
的形式向主体中的访问令牌发出对{ 'access_token': authToken.accessToken }
的POST请求。我应该获得一个身份验证令牌,然后可以使用该令牌来调用实际功能,但是无论尝试什么,我总是得到相同的响应: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设置的概述,对此非常重要(如果我忘记了任何设置,请随时提醒我)。
天蓝色功能:
激活身份验证,仅AAD身份验证,高级设置: this tutorial from MS
Azure功能-应用注册:
公开API-公开user_impersonation API,以便Web应用可以使用它:
Azure静态Web应用程序(React SPA)-应用程序注册:
应用程序URI ID,在Azure功能中用作令牌受众(高级身份验证设置):
API权限-使用Azure功能应用程序注册公开的user_impersonation API:
如果此配置有任何错误,请告诉我。很有可能是这样,但是自从我按照MSDN上的教程学习以来,我不知道发生什么。后来我只添加了user_impersonation,因为它不起作用。
答案 0 :(得分:1)
根据提供的信息,您没有在authProvider
文件中配置权限范围。您需要在创建AD应用程序时添加定义的作用域以保护功能。因此,请在scopes: ["openid","<your function app scope>"]
中将范围更新为authProvider
。
例如
创建Azure AD应用程序以保护功能
在App Service应用中启用Azure Active Directory
创建客户端AD应用程序以访问功能
代码
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",
}
);
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);
};
答案 1 :(得分:0)
我处理了同样的问题有一段时间了。如果您确定获得了正确的访问令牌并正确传递了它,请查看门户中的配置。如果您自动为函数应用创建了应用注册,请检查 ISSUER URL 的设置方式。您可以在功能 app>authentication>edit 中找到它。确保 url 末尾没有 /v2.0。 Azure 函数仅适用于默认 (/v1.0) 路由。