MSAL Authentication in React: Complete Guide – Part 2: MSAL Provider and Authentication Context

MSAL Authentication in React: Complete Guide – Part 2: MSAL Provider and Authentication Context

This entry is part 2 of 5 in the series Mastering MSAL Authentication in React Applications

Welcome back to our MSAL authentication series! In Part 1, we covered the basics of MSAL and Azure AD setup. Now, let’s implement the MSAL Provider and create the authentication context in your React application.

Setting Up the MSAL Provider

The MSAL Provider is the foundation of MSAL authentication in React. It wraps your application and provides authentication context to all child components.

Creating the MSAL Instance

First, let’s create the MSAL instance in your main application file:

// src/index.js or src/main.jsx
import React from 'react';
import ReactDOM from 'react-dom/client';
import { PublicClientApplication } from '@azure/msal-browser';
import { MsalProvider } from '@azure/msal-react';
import { msalConfig } from './config/authConfig';
import App from './App';

// Create MSAL instance
const msalInstance = new PublicClientApplication(msalConfig);

// Initialize MSAL
await msalInstance.initialize();

const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
  <MsalProvider instance={msalInstance}>
    <App />
  </MsalProvider>
);

Handling MSAL Initialization

For applications using redirect flow, it’s crucial to handle the redirect promise:

// Enhanced initialization
const msalInstance = new PublicClientApplication(msalConfig);

// Handle redirect promise
msalInstance.initialize().then(() => {
  // Check if this is a redirect from authentication
  return msalInstance.handleRedirectPromise();
}).then((tokenResponse) => {
  if (tokenResponse) {
    console.log('Authentication successful:', tokenResponse);
  }
  
  // Render the app
  const root = ReactDOM.createRoot(document.getElementById('root'));
  root.render(
    <MsalProvider instance={msalInstance}>
      <App />
    </MsalProvider>
  );
}).catch((error) => {
  console.error('MSAL initialization failed:', error);
});

Creating an Authentication Context

While MSAL React provides built-in hooks, creating a custom authentication context gives you more control over the authentication state and user experience.

// src/contexts/AuthContext.js
import React, { createContext, useContext, useEffect, useState } from 'react';
import { useMsal, useIsAuthenticated } from '@azure/msal-react';
import { loginRequest } from '../config/authConfig';

const AuthContext = createContext();

export const useAuth = () => {
  const context = useContext(AuthContext);
  if (!context) {
    throw new Error('useAuth must be used within an AuthProvider');
  }
  return context;
};

export const AuthProvider = ({ children }) => {
  const { instance, accounts } = useMsal();
  const isAuthenticated = useIsAuthenticated();
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    if (isAuthenticated && accounts.length > 0) {
      setUser(accounts[0]);
    } else {
      setUser(null);
    }
    setLoading(false);
  }, [isAuthenticated, accounts]);

  const login = async () => {
    try {
      setLoading(true);
      await instance.loginRedirect(loginRequest);
    } catch (error) {
      console.error('Login failed:', error);
      setLoading(false);
    }
  };

  const logout = async () => {
    try {
      setLoading(true);
      await instance.logoutRedirect({
        postLogoutRedirectUri: window.location.origin
      });
    } catch (error) {
      console.error('Logout failed:', error);
      setLoading(false);
    }
  };

  const value = {
    user,
    isAuthenticated,
    loading,
    login,
    logout
  };

  return (
    <AuthContext.Provider value={value}>
      {children}
    </AuthContext.Provider>
  );
};

Using MSAL Hooks

MSAL React provides several useful hooks for authentication management:

Essential MSAL Hooks

  • useMsal(): Access to MSAL instance and accounts
  • useIsAuthenticated(): Boolean indicating authentication status
  • useAccount(): Get specific account information
  • useMsalAuthentication(): Handle authentication with specific requests
// Example component using MSAL hooks
import React from 'react';
import { useMsal, useIsAuthenticated } from '@azure/msal-react';

const UserProfile = () => {
  const { accounts } = useMsal();
  const isAuthenticated = useIsAuthenticated();

  if (!isAuthenticated) {
    return <div>Please sign in to view your profile.</div>;
  }

  const account = accounts[0];

  return (
    <div className="user-profile">
      <h2>Welcome, {account.name}!</h2>
      <p>Email: {account.username}</p>
      <p>Account ID: {account.homeAccountId}</p>
    </div>
  );
};

export default UserProfile;

Creating Authentication Components

Let’s create reusable authentication components for login and logout functionality:

Login Button Component

// src/components/LoginButton.js
import React from 'react';
import { useMsal } from '@azure/msal-react';
import { loginRequest } from '../config/authConfig';

const LoginButton = ({ className = '' }) => {
  const { instance } = useMsal();

  const handleLogin = async () => {
    try {
      await instance.loginRedirect(loginRequest);
    } catch (error) {
      console.error('Login error:', error);
    }
  };

  return (
    <button 
      className={`login-button ${className}`}
      onClick={handleLogin}
      type="button"
    >
      Sign In with Microsoft
    </button>
  );
};

export default LoginButton;

Logout Button Component

// src/components/LogoutButton.js
import React from 'react';
import { useMsal } from '@azure/msal-react';

const LogoutButton = ({ className = '' }) => {
  const { instance } = useMsal();

  const handleLogout = async () => {
    try {
      await instance.logoutRedirect({
        postLogoutRedirectUri: window.location.origin
      });
    } catch (error) {
      console.error('Logout error:', error);
    }
  };

  return (
    <button 
      className={`logout-button ${className}`}
      onClick={handleLogout}
      type="button"
    >
      Sign Out
    </button>
  );
};

export default LogoutButton;

Handling Authentication States

Create a component that handles different authentication states:

// src/components/AuthenticationWrapper.js
import React from 'react';
import { useIsAuthenticated, useMsal } from '@azure/msal-react';
import LoginButton from './LoginButton';
import LoadingSpinner from './LoadingSpinner';

const AuthenticationWrapper = ({ children }) => {
  const isAuthenticated = useIsAuthenticated();
  const { inProgress } = useMsal();

  // Show loading spinner during authentication process
  if (inProgress === "login" || inProgress === "logout") {
    return <LoadingSpinner message="Authenticating..." />;
  }

  // Show login prompt if not authenticated
  if (!isAuthenticated) {
    return (
      <div className="auth-prompt">
        <h2>Authentication Required</h2>
        <p>Please sign in to access this application.</p>
        <LoginButton />
      </div>
    );
  }

  // Render protected content
  return <>{children}</>;
};

export default AuthenticationWrapper;

Error Handling

Implement proper error handling for authentication failures:

// src/hooks/useAuthError.js
import { useState, useEffect } from 'react';
import { EventType } from '@azure/msal-browser';
import { useMsal } from '@azure/msal-react';

export const useAuthError = () => {
  const [error, setError] = useState(null);
  const { instance } = useMsal();

  useEffect(() => {
    const callbackId = instance.addEventCallback((event) => {
      if (event.eventType === EventType.LOGIN_FAILURE ||
          event.eventType === EventType.LOGOUT_FAILURE ||
          event.eventType === EventType.ACQUIRE_TOKEN_FAILURE) {
        setError(event.error);
      }
      
      if (event.eventType === EventType.LOGIN_SUCCESS ||
          event.eventType === EventType.LOGOUT_SUCCESS) {
        setError(null);
      }
    });

    return () => {
      if (callbackId) {
        instance.removeEventCallback(callbackId);
      }
    };
  }, [instance]);

  const clearError = () => setError(null);

  return { error, clearError };
};

Best Practices

  • Always handle redirect promise: Essential for redirect flow
  • Use session storage: Safer than localStorage for tokens
  • Implement loading states: Better user experience during auth
  • Handle errors gracefully: Provide meaningful error messages
  • Clear error states: Reset errors on successful authentication

What’s Next?

In Part 3, we’ll dive into implementing protected routes and route guards to secure specific pages in your application. We’ll cover:

  • Creating protected route components
  • Implementing route guards
  • Handling unauthorized access
  • Role-based access control

Stay tuned for the next installment of our MSAL authentication series!

Navigate<< MSAL Authentication in React: Complete Guide – Part 1: Introduction and SetupMSAL Authentication in React: Complete Guide – Part 3: Protected Routes and Route Guards >>

Written by:

265 Posts

View All Posts
Follow Me :
How to whitelist website on AdBlocker?

How to whitelist website on AdBlocker?

  1. 1 Click on the AdBlock Plus icon on the top right corner of your browser
  2. 2 Click on "Enabled on this site" from the AdBlock Plus option
  3. 3 Refresh the page and start browsing the site