Functionality: ReactJS MSAL SSO login, logout, refresh token and redirect to login page once token has expired.
Issue: Unable to redirect to login if the token has expired (using MSAL SSO for ReactJS).
Reference taken from this link - How to handle token expiry in azure msal react?
The below code works great for login, logout, token refresh. However, unable to redirect to login if the token has expired: -
App.js: -
import { MsalProvider } from "@azure/msal-react";
import MainContent from "./views/MainContent";
import "./App.css";
const App = ({ instance }) => {
return (
<MsalProvider instance={instance}>
<MainContent></MainContent>
</MsalProvider>
);
};
export default App;
MainContent.js: -
import React, { useEffect } from "react";
import {
AuthenticatedTemplate,
useMsal,
UnauthenticatedTemplate,
} from "@azure/msal-react";
import { useShallow } from "zustand/react/shallow";
import useAuthStore from "../store/authStore";
import { jwtDecode } from "jwt-decode";
import AppRoutes from "../routes/AppRoutes";
import checkUserPermissions from "../utils/checkUserPermissions";
import AutoSignIn from "./AutoSignIn";
import { InteractionRequiredAuthError } from "@azure/msal-browser";
const MainContent = () => {
const { instance } = useMsal();
const activeAccount = instance.getActiveAccount();
const REFRESH_THRESHOLD = 600; // 10 mins (60 seconds * 10 mins = 600)
const { setLoginState } = useAuthStore(
useShallow((state) => ({
setLoginState: state.setLoginState,
}))
);
const { isLoggedOut } = useAuthStore(
useShallow((state) => ({
isLoggedOut: state.isLoggedOut,
}))
);
const saveActiveToken = (token) => {
if (token) {
sessionStorage.setItem("access-token", token);
}
};
if (activeAccount?.idToken && typeof activeAccount?.idToken != "undefined") {
saveActiveToken(activeAccount?.idToken);
}
const handleLogoutRedirect = () => {
instance
.logoutRedirect()
.catch((error) => console.log("logout error: ", error));
console.log(instance.getAllAccounts());
debugger
};
const refreshToken = async () => {
await instance.acquireTokenSilent(activeAccount).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
await instance.acquireTokenRedirect(activeAccount);
}
});
saveActiveToken(activeAccount?.idToken);
};
useEffect(() => {
if (isLoggedOut) handleLogoutRedirect();
}, [isLoggedOut]);
useEffect(() => {
setInterval(() => {
try {
const token = sessionStorage.getItem("access-token");
if (typeof token != "undefined" && token != "") {
const decodedToken = jwtDecode(token);
const currentTime = Math.floor(Date.now() / 1000); // Current time in seconds
const timeUntilExpiry = decodedToken.exp - currentTime;
if (timeUntilExpiry <= REFRESH_THRESHOLD) {
refreshToken();
}
}
} catch (err) {
console.log("Printing setInterval() Error: ", err);
}
}, 60000);
}, []);
return (
<div>
<AuthenticatedTemplate>
{activeAccount ? <AppRoutes /> : null}
</AuthenticatedTemplate>
<UnauthenticatedTemplate>
<AutoSignIn instance={instance} />
</UnauthenticatedTemplate>
</div>
);
};
export default MainContent;
var request = {
scopes: ["Mail.Read"],
account: currentAccount,
forceRefresh: true
refreshTokenExpirationOffsetSeconds: 7200 // 2 hours * 60 minutes * 60 seconds = 7200 seconds
};
const tokenResponse = await msalInstance.acquireTokenSilent(request).catch(async (error) => {
if (error instanceof InteractionRequiredAuthError) {
// fallback to interaction when silent call fails
await msalInstance.acquireTokenRedirect(request);
}
});
add this function into useEffect and update the component
import React, { useEffect } from "react";
import { useMsal } from "@azure/msal-react";
import { InteractionRequiredAuthError } from "@azure/msal-browser";
const YourComponent = () => {
const { instance, accounts } = useMsal();
useEffect(() => {
const checkTokenExpiration = async () => {
if (accounts.length > 0) {
const currentAccount = accounts[0];
const request = {
scopes: ["Mail.Read"],
account: currentAccount,
forceRefresh: true, // Forces to refresh token
refreshTokenExpirationOffsetSeconds: 7200, // 2 hours
};
try {
// Try to acquire a token silently
const tokenResponse = await instance.acquireTokenSilent(request);
console.log("Token acquired:", tokenResponse);
} catch (error) {
if (error instanceof InteractionRequiredAuthError) {
// Fallback to interactive token acquisition when silent call fails
await instance.acquireTokenRedirect(request);
} else {
console.error("Error acquiring token silently:", error);
}
}
}
};
checkTokenExpiration();
}, [accounts, instance]);
return <div>{/* Your component content */}</div>;
};
export default YourComponent;