如何将 Azure AD 集成到一个消费 Azure REST API 的 React Web 应用程序中

19

我有一个基于React的Web应用程序,已经为该Web应用程序配置了Azure AD身份验证。它是100%的客户端应用程序,没有服务器端组件。

我使用了这个组件: https://github.com/salvoravida/react-adal

我的代码如下: adalconfig.js

import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';

export const adalConfig = {
  tenant: 'mytenantguid',
  clientId: 'myappguid',
  endpoints: {
    api: '14d71d65-f596-4eae-be30-27f079bf8d4b',
  },
  cacheLocation: 'localStorage',
};

export const authContext = new AuthenticationContext(adalConfig);

export const adalApiFetch = (fetch, url, options) =>
  adalFetch(authContext, adalConfig.endpoints.api, fetch, url, options);

export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);

index.js

import React from 'react';
import ReactDOM from 'react-dom';
import DashApp from './dashApp';
import registerServiceWorker from './registerServiceWorker';
import 'antd/dist/antd.css';

import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';

const DO_NOT_LOGIN = false;

runWithAdal(authContext, () => {

  ReactDOM.render(<DashApp />, document.getElementById('root'));

  // Hot Module Replacement API
  if (module.hot) {
    module.hot.accept('./dashApp.js', () => {
      const NextApp = require('./dashApp').default;
      ReactDOM.render(<NextApp />, document.getElementById('root'));
    });
  }

},DO_NOT_LOGIN);


registerServiceWorker();

dashapp.js

import React from "react";
import { Provider } from "react-redux";
import { store, history } from "./redux/store";
import PublicRoutes from "./router";
import { ThemeProvider } from "styled-components";
import { LocaleProvider } from "antd";
import { IntlProvider } from "react-intl";
import themes from "./settings/themes";
import AppLocale from "./languageProvider";
import config, {
  getCurrentLanguage
} from "./containers/LanguageSwitcher/config";
import { themeConfig } from "./settings";
import DashAppHolder from "./dashAppStyle";
import Boot from "./redux/boot";

const currentAppLocale =
  AppLocale[getCurrentLanguage(config.defaultLanguage || "english").locale];


const DashApp = () => (
  <LocaleProvider locale={currentAppLocale.antd}>
    <IntlProvider
      locale={currentAppLocale.locale}
      messages={currentAppLocale.messages}
    >
      <ThemeProvider theme={themes[themeConfig.theme]}>
        <DashAppHolder>
          <Provider store={store}>
            <PublicRoutes history={history} />
          </Provider>
        </DashAppHolder>
      </ThemeProvider>
    </IntlProvider>
  </LocaleProvider>
);
Boot()
  .then(() => DashApp())
  .catch(error => console.error(error));

export default DashApp;
export { AppLocale };

到这一步为止,所有的东西都很好,当用户未经身份验证时,会被重定向到login.live.com进行身份验证,然后再重定向回来。

但是,我还创建了另一个Azure Web应用程序来托管REST API,该REST API已在Azure AD中配置,因此尝试使用REST的用户将需要进行身份验证。

现在的问题是:如何设置我的客户端应用程序以消耗由Azure AD保护的REST API?

我找到了这个,并且看起来正是我要找的,但我不确定如何将其集成到我上面的现有代码中。

https://github.com/AzureAD/azure-activedirectory-library-for-js/issues/481

更新: 针对潜在读者

这篇答案以及此URL上的配置应用程序注册说明帮助我解决了问题:https://blog.ithinksharepoint.com/2016/05/16/dev-diary-s01e06-azure-mvc-web-api-angular-and-adal-js-and-401s/


请查看此链接,可能会对您有所帮助:https://azure.microsoft.com/zh-cn/resources/videos/protecting-web-api-backend-with-azure-active-directory-and-api-management/ - souravlahoti
3个回答

13
关键在于adalConfig.js中定义的adalApiFetch。正如您所看到的,它只是adalFetch的简单包装器。这个方法(在react-adal中定义)接收一个ADAL实例(authContext)、一个资源标识符(resourceGuiId)、一个方法(fetch)、一个URL(url)和一个对象(options)。该方法执行以下操作:
  1. 使用ADAL实例(authContext)获取由resourceGuiId标识的资源的访问令牌。
  2. 将此访问令牌添加到options对象的headers字段中(如果未提供,则创建一个options对象)。
  3. 调用给定的“fetch”方法,将urloptions对象作为参数传递。
adalApiFetch方法(您在adalConfig.js中定义)只是使用在adalConfig.endpoints.api中标识的资源调用adalFetch
好的,那么如何使用所有这些来进行REST请求,并在React应用程序中使用响应?让我们使用一个示例。在以下示例中,我们将使用Microsoft Graph API作为Azure AD受保护的REST API。我们将通过其友好标识符URI("https://graph.microsoft.com")来标识它,但请记住,这也可以是Guid应用程序ID。 adalConfig.js定义了ADAL配置,并导出了一些辅助方法:
import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';

export const adalConfig = {
tenant: '{tenant-id-or-domain-name}',
clientId: '{app-id-of-native-client-app}',
endpoints: {
    api: 'https://graph.microsoft.com' // <-- The Azure AD-protected API
},
cacheLocation: 'localStorage',
};

export const authContext = new AuthenticationContext(adalConfig);

export const adalApiFetch = (fetch, url, options) =>
adalFetch(authContext, adalConfig.endpoints.api, fetch, url, options);

export const withAdalLoginApi = withAdalLogin(authContext, adalConfig.endpoints.api);

index.js使用react-adal中的runWithAdal方法包装indexApp.js,以确保用户在加载indexApp.js之前已经使用Azure AD进行了签名:

import { runWithAdal } from 'react-adal';
import { authContext } from './adalConfig';

const DO_NOT_LOGIN = false;

runWithAdal(authContext, () => {

// eslint-disable-next-line
require('./indexApp.js');

},DO_NOT_LOGIN);

indexApp.js 简单地加载并渲染了一个 App 实例,没有什么花哨的东西:

import React from 'react';
import ReactDOM from 'react-dom';
import './index.css';
import App from './App';
import registerServiceWorker from './registerServiceWorker';

ReactDOM.render(<App />, document.getElementById('root'));
registerServiceWorker();

App.js 是一个简单的组件,魔法就是在这里发生的:

  • 我们定义了一个 state 值。在这种情况下,它被称为 apiResponse,因为我们只显示原始的 API 响应,但当然你可以将此状态命名为任何你想要的名称(或拥有多个状态值)。
  • componentDidMount 中(在元素可用于 DOM 后运行),我们调用 adalApiFetch。我们传入 fetch(来自Fetch APIfetch参数)和我们想要进行 REST 请求的端点(在这种情况下是 Microsoft Graph 的 /me 端点):
  • render 方法中,我们只需在一个 <pre> 元素中显示此状态值即可。
import React, { Component } from 'react';
import { adalApiFetch } from './adalConfig';

class App extends Component {

  state = {
    apiResponse: ''
  };

  componentDidMount() {

    // We're using Fetch as the method to be called, and the /me endpoint 
    // from Microsoft Graph as the REST API request to make.
    adalApiFetch(fetch, 'https://graph.microsoft.com/v1.0/me', {})
      .then((response) => {

        // This is where you deal with your API response. In this case, we            
        // interpret the response as JSON, and then call `setState` with the
        // pretty-printed JSON-stringified object.
        response.json()
          .then((responseJson) => {
            this.setState({ apiResponse: JSON.stringify(responseJson, null, 2) })
          });
      })
      .catch((error) => {

        // Don't forget to handle errors!
        console.error(error);
      })
  }

  render() {
    return (
      <div>
        <p>API response:</p>
        <pre>{ this.state.apiResponse }</pre>
      </div>
    );
  }
}

export default App;

我遇到了这个错误:未处理的拒绝(SyntaxError):JSON中位置为0的意外令牌< (匿名函数) src / containers / testREST.js:28 - Luis Valencia
我无法对我看不到的代码发表评论。你能分享一下出错的代码吗? - Philippe Signoret
哦,实际上,我可以看到足够的错误信息来猜测问题所在。你的 API (/values) 返回的是 JSON 吗?response.json() 假设它是 JSON,并试图对其进行反序列化,但发现了一个 < 字符。也许 /values 返回的是 HTML 或 XML? - Philippe Signoret
是的,这也是我的想法,我只是将格式化程序从XML更改为JSON,并测试了React应用程序。 - Luis Valencia
那就是问题所在,非常感谢,经过一周的挣扎终于解决了。 - Luis Valencia
此帖子还帮助我解决了配置部分的问题。https://blog.ithinksharepoint.com/2016/05/16/dev-diary-s01e06-azure-mvc-web-api-angular-and-adal-js-and-401s/ - Luis Valencia

0

我仍然有上面给出的配置问题。我添加了更多的配置并且它起作用了。希望这会有所帮助。

import { AuthenticationContext, adalFetch, withAdalLogin } from 'react-adal';

export const adalConfig = {
tenant: '{tenant-id-or-domain-name}',
clientId: '{app-id-of-native-client-app}',
endpoints: {
    api: 'https://graph.microsoft.com'
},
cacheLocation: 'localStorage',
extraQueryParameter: 'prompt=admin_consent'
};

export const authContext = new AuthenticationContext(adalConfig);

0

Phillipe的回复让我找对了方向,但是我的token仍然无法被接受。

aadsTS700051: 应用程序未启用'response_type'为'token'。

为了解决这个问题,我需要进入我的应用程序注册表 > 清单,并将oauth2AllowImplicitFlow设置为true:

    "oauth2AllowImplicitFlow": true,

退出您的Azure帐户,重新登录,您应该能够收到用户的详细信息。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接