Forem

Mohsen Kokabi
Mohsen Kokabi

Posted on

8 3

React Hook login using Azure Active Directory

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

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read more →

Top comments (0)

Billboard image

The Next Generation Developer Platform

Coherence is the first Platform-as-a-Service you can control. Unlike "black-box" platforms that are opinionated about the infra you can deploy, Coherence is powered by CNC, the open-source IaC framework, which offers limitless customization.

Learn more

👋 Kindness is contagious

Discover a treasure trove of wisdom within this insightful piece, highly respected in the nurturing DEV Community enviroment. Developers, whether novice or expert, are encouraged to participate and add to our shared knowledge basin.

A simple "thank you" can illuminate someone's day. Express your appreciation in the comments section!

On DEV, sharing ideas smoothens our journey and strengthens our community ties. Learn something useful? Offering a quick thanks to the author is deeply appreciated.

Okay