How to integrate azure ad into a react web app that consumes a REST API in azure too

Luis Valencia picture Luis Valencia · May 12, 2018 · Viewed 19.8k times · Source

I have one web app which is React, and I already configured Azure AD Authentication for the web app itself. Its 100% Client site app, no server side components.

I used this component: https://github.com/salvoravida/react-adal

My code is as follows: 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 };

Until that point everything works fine, when the user is not authenticated its redirected to login.live.com for authentication and then its redirected back.

However I also created another azure webapp for hosting a REST API, that REST API is already configured in Azure AD, so that users that try to use the rest will need to be authenticated.

Now the question is: How do I setup my client side APP to consume REST API which is protected by Azure AD.?

I found this and looks what I am looking for, but I am not sure how to integrate this into my existing code above

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

Update: For potential readers

This answer plus the instructions on this url to configure App registrations helped me to solve the problem: https://blog.ithinksharepoint.com/2016/05/16/dev-diary-s01e06-azure-mvc-web-api-angular-and-adal-js-and-401s/

Answer

Philippe Signoret picture Philippe Signoret · May 21, 2018

The key here is adalApiFetch, defined in adalConfig.js. As you can see, it's a simple wrapper around adalFetch. This method (defined in react-adal) receives an ADAL instance (authContext), a resource identifier (resourceGuiId), a method (fetch), a URL (url) and an object (options). The method does the following:

  1. Use the ADAL instance (authContext) to obtain an access token for the resource identified by resourceGuiId.
  2. Add this access token to the headers field of the options object (or create one if it wasn't provided).
  3. Call the given "fetch" method passing in url and the options object as parameters.

The adalApiFetch method (which you have defined in adalConfig.js) simply calls adalFetch with the resource identified in adalConfig.endpoints.api.

Ok, so how do you use all of this to make a REST request, and consume the response in your React app? Let's use an example. In the following example, we will be using the Microsoft Graph API as the Azure AD-protected REST API. We will be identifying it by it's friendly identifier URI ("https://graph.microsoft.com"), but just keep in mind that that could just as well be the Guid app ID.

adalConfig.js defines the ADAL configuration, and exports a couple helper methods:

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 wraps indexApp.js with the runWithAdal method from react-adal, which ensures the user is signed with Azure AD before loading indexApp.js:

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 simply loads and renders an instance of App, nothing fancy here:

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 is a simple component where the magic happens:

  • We define a state value. In this case, it's called apiResponse since we're just displaying the raw API response, but of course you could name this state whatever you wanted (or have multiple state values).
  • During componentDidMount (which is run after the element is available in the DOM), we make a call to the adalApiFetch. We pass in fetch (from the Fetch API as the fetch parameter, and the endpoint for the REST request we want to make (the /me endpoint in Microsoft Graph, in this case):
  • In the render method, we simply display this state value in a <pre> element.
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;