loading...

React Hook login using Azure Active Directory

mkokabi profile image Mohsen Kokabi ・3 min read

There is a great example here 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

Step 2

Create a react application using

npx create-react-app azure-ad-react
cd ./azure-ad-react

using npm or yarn add the following dependencies:

    "@microsoft/microsoft-graph-client": "^2.0.0",
    "msal": "^1.2.1",

Add the config.json to your src folder.

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

src\GraphService.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

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.

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 or you can add the eslint to ignore it.

Image 1: Before login

Before login

Image 2: Login would take to the Azure login page

login

Image 3: You might see different page of your organization login process and first time would see the contest pages to allow access.

login

Image 4: Then get redirected to the application.

logout

Image 5: If you have logged in with more than one account it would ask which one to logout.

logout

Posted on by:

Discussion

pic
Editor guide