- MSAL Authentication in React: Complete Guide – Part 1: Introduction and Setup
- MSAL Authentication in React: Complete Guide – Part 2: MSAL Provider and Authentication Context
- MSAL Authentication in React: Complete Guide – Part 3: Protected Routes and Route Guards
- MSAL Authentication in React: Complete Guide – Part 4: Token Management and API Calls
- MSAL Authentication in React: Complete Guide – Part 5: Advanced Topics and Production Considerations
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!