React Hook login using Azure Active Directory 
Tags: react, azure, hook
Published: 2020-02-07 06:02:52

There is a great example [here](https://docs.microsoft.com/en-us/graph/tutorials/react) which inspired me. 
I have removed the calendar section and migrated the React part to Hooks.

### Step 1, Azure application registration
This part could be exactly like [Microsoft tutorial](https://docs.microsoft.com/en-us/graph/tutorials/react?tutorial-step=2)

### Step 2
Create a react application using 
```powershell
npx create-react-app azure-ad-react
cd ./azure-ad-react
```
using npm or yarn add the following dependencies:
```json
    "@microsoft/microsoft-graph-client": "^2.0.0",
    "msal": "^1.2.1",
```
Add the config.json to your src folder.
```js
module.exports = {
    appId: '{Your azure application (client) id}',
    redirectUri: 'http://localhost:3000/',
    scopes: [
        "user.read"
    ]
};
```
note: You can get the Application (client) ID from *Azure* portal from the *Overview* tab of your *App-registrations* panel.
![Azure portal](https://docs.microsoft.com/en-us/graph/tutorials/react/tutorial/images/aad-application-id.png)


src\GraphService.js
```js
var graph = require('@microsoft/microsoft-graph-client');

function getAuthenticatedClient(accessToken) {
  // Initialize Graph client
  const client = graph.Client.init({
    // Use the provided access token to authenticate
    // requests
    authProvider: (done) => {
      done(null, accessToken.accessToken);
    }
  });

  return client;
}

export async function getUserDetails(accessToken) {
  const client = getAuthenticatedClient(accessToken);

  const user = await client.api('/me').get();
  return user;
}
```

src\App.js
```js
import React, { useEffect, useState } from "react";

import "./App.css";
import { UserAgentApplication } from "msal";
import { getUserDetails } from "./GraphService";
import config from "./Config";

function App() {
  const userAgentApplication = new UserAgentApplication({
    auth: {
      clientId: config.appId,
      redirectUri: config.redirectUri
    },
    cache: {
      cacheLocation: "localStorage",
      storeAuthStateInCookie: true
    }
  });

  const [loginState, setLoginState] = useState({
    isAuthenticated: false,
    user: {},
    error: null
  });

  useEffect(() => {
    let user = userAgentApplication.getAccount();
    console.log(user);
    if (user) {
      // Enhance user object with data from Graph
      getUserProfile();
    }
  }, []);

  const login = async () => {
    try {
      await userAgentApplication.loginPopup({
        scopes: config.scopes,
        prompt: "select_account"
      });
      await getUserProfile();
    } catch (err) {
      var error = {};

      if (typeof err === "string") {
        var errParts = err.split("|");
        error =
          errParts.length > 1
            ? { message: errParts[1], debug: errParts[0] }
            : { message: err };
      } else {
        error = {
          message: err.message,
          debug: JSON.stringify(err)
        };
      }

      setLoginState({
        isAuthenticated: false,
        user: {},
        error: error
      });
    }
  };

  const logout = () => {
    userAgentApplication.logout();
  };

  const getUserProfile = async () => {
    try {
      // Get the access token silently
      // If the cache contains a non-expired token, this function
      // will just return the cached token. Otherwise, it will
      // make a request to the Azure OAuth endpoint to get a token

      var accessToken = await userAgentApplication.acquireTokenSilent({
        scopes: config.scopes
      });

      if (accessToken) {
        // Get the user's profile from Graph
        var user = await getUserDetails(accessToken);
        setLoginState({
          isAuthenticated: true,
          user: {
            displayName: user.displayName,
            email: user.mail || user.userPrincipalName,
            givenName: user.givenName,
            surname: user.surname
          },
          error: null
        });
      }
    } catch (err) {
      var error = {};
      if (typeof err === "string") {
        var errParts = err.split("|");
        error =
          errParts.length > 1
            ? { message: errParts[1], debug: errParts[0] }
            : { message: err };
      } else {
        error = {
          message: err.message,
          debug: JSON.stringify(err)
        };
      }

      setLoginState({
        isAuthenticated: false,
        user: {},
        error: error
      });
    }
  };

  return (
    <div>
      <p>Display name: {loginState.user.displayName}</p>
      <p>Username: {loginState.user.userName}</p>
      <p>First name: {loginState.user.givenName}</p>
      <p>Last name: {loginState.user.surname}</p>
      {loginState.error ? <p>loginState.error</p> : null}
      {loginState.isAuthenticated ? (
        <div>
          <h4>Welcome {loginState.user.displayName}!</h4>
          <button color="primary" onClick={logout}>
            Logout
          </button>
        </div>
      ) : (
        <button color="primary" onClick={login}>
          Click here to sign in
        </button>
      )}
    </div>
  );
}

export default App;
```

### Step 3: Running the application.
```powershell
npm start
```
Be aware you would get a warning while running this application:
```
Compiled with warnings.

./src/App.js
  Line 35:6:  React Hook useEffect has missing dependencies: 'getUserProfile' and 'userAgentApplication'. Either include them or remove the dependency array  react-hooks/exhaustive-deps

Search for the keywords to learn more about each warning.
To ignore, add // eslint-disable-next-line to the line before.
```
The reason for this warning is the code is using *getUserProfile* and *userAgentApplication* inside the useEffect. You can read more about it [here](https://reactjs.org/docs/hooks-faq.html#is-it-safe-to-omit-functions-from-the-list-of-dependencies) or you can add the eslint to ignore it. 

### Image 1: Before login
![Before login](https://github.com/mkokabi/images/blob/master/AzureReactHook/image1.png?raw=true)

### Image 2: Login would take to the Azure login page
![login](https://github.com/mkokabi/images/blob/master/AzureReactHook/image2.png?raw=true)

### Image 3: You might see different page of your organization login process and first time would see the contest pages to allow access. 
![login](https://github.com/mkokabi/images/blob/master/AzureReactHook/image3.png?raw=true)

### Image 4: Then get redirected to the application.
![logout](https://github.com/mkokabi/images/blob/master/AzureReactHook/image4.png?raw=true)

### Image 5: If you have logged in with more than one account it would ask which one to logout.
![logout](https://github.com/mkokabi/images/blob/master/AzureReactHook/image5.png?raw=true)