从 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应用保护功能

    1. 注册 Azure AD 应用程序。完成后,请复制 应用程序(客户端)ID目录(租户)ID

    2. 配置重定向 URI。 Select Web 并输入 <app-url>/.auth/login/aad/callback.

    3. 启用隐式授权流程

    4. 定义API范围并复制

    5. 创建客户端密钥。

  • 在应用服务应用程序中启用 Azure Active Directory

  • 创建客户端AD应用访问功能

    1. 注册申请
    2. 启用隐式授权流程
    3. 配置API权限。让您的客户端应用程序具有访问功能的权限
  • 代码

    1. 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",
  }
);


  1. 致电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) 路由。