从React SPA / Azure静态Web应用程序调用受Azure AD保护的Azure函数

时间:2020-09-28 08:00:49

标签: reactjs azure azure-active-directory azure-functions azure-static-web-app

我正在使用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
}

authProviderreact-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功能-应用注册:

身份验证设置: enter image description here

客户机密: enter image description here

公开API-公开user_impersonation API,以便Web应用可以使用它: enter image description here

Azure静态Web应用程序(React SPA)-应用程序注册:

应用程序URI ID,在Azure功能中用作令牌受众(高级身份验证设置): enter image description here

API权限-使用Azure功能应用程序注册公开的user_impersonation API:

enter image description here

如果此配置有任何错误,请告诉我。很有可能是这样,但是自从我按照MSDN上的教程学习以来,我不知道发生什么。后来我只添加了user_impersonation,因为它不起作用。

2 个答案:

答案 0 :(得分:1)

根据提供的信息,您没有在authProvider文件中配置权限范围。您需要在创建AD应用程序时添加定义的作用域以保护功能。因此,请在scopes: ["openid","<your function app scope>"]中将范围更新为authProvider

例如

  • 创建Azure AD应用程序以保护功能

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

    2. 配置重定向URI 。选择网络,然后输入<app-url>/.auth/login/aad/callback

    3. 启用隐式授予流程

    4. 定义API范围并复制它 enter image description here

    5. 创建客户端机密。

  • 在App Service应用中启用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);
  };

enter image description here

答案 1 :(得分:0)

我处理了同样的问题有一段时间了。如果您确定获得了正确的访问令牌并正确传递了它,请查看门户中的配置。如果您自动为函数应用创建了应用注册,请检查 ISSUER URL 的设置方式。您可以在功能 app>authentication>edit 中找到它。确保 url 末尾没有 /v2.0。 Azure 函数仅适用于默认 (/v1.0) 路由。