Iam creating a React application with Next.js
14 and App router
. Iam trying single Sign On (SSO)
using Azure Msal.
I have created my Msal.ts which has MSAL configuration which iam using it my layout.tsx
which is a wrapper for my web application.
When i tried to trigger Login
., i see following error
BrowserAuthError: uninitialized_public_client_application: You must call and await the initialize function before attempting to call any other MSAL API.
This is my Msal.ts
code:
import {
PublicClientApplication,
Configuration,
LogLevel,
} from "@azure/msal-browser";
const msalConfig: Configuration = {
auth: {
clientId: process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID || "default-client-id", // Azure AD client id
authority: `https://login.microsoftonline.com/${process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID}`, // zure AD tenant id
redirectUri: process.env.NEXT_PUBLIC_APP_REDIRECT_URL, // app redirect URL
postLogoutRedirectUri: "/", // app logout URL
},
cache: {
cacheLocation: "sessionStorage", // This configures where your cache will be stored
storeAuthStateInCookie: false, // If you are having issues on IE11, you may need to set this to true
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
}
},
logLevel: LogLevel.Info,
piiLoggingEnabled: false,
},
},
};
const msalInstance = new PublicClientApplication(msalConfig);
export default msalInstance;
This Msal.ts
., iam importing it to my Layout.tsx
:
"use client";
import React, { useEffect, useRef } from "react";
import "./globals.css";
import Header from "./components/Header";
import { usePathname, useRouter } from "next/navigation";
import { Box, Grid } from "@mui/material";
import { AppStore, makeStore, persistor } from "@/lib/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";
import Leftsidebar from "./components/Leftsidebar";
import msalInstance from "@/lib/Msal";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const isAuthenticated = async () => {
const accounts = await msalInstance.getAllAccounts();
console.log("accounts", accounts);
return accounts.length > 0;
};
const router = useRouter();
const pathName = usePathname();
useEffect(() => {
const checkAuth = async () => {
const auth = await isAuthenticated();
// restrict unauthorized users to access inner pages
if (!auth && pathName !== "/") {
router.push("/");
}
// restrict authorized users to signout before login again
if (auth && pathName === "/") {
router.push("/dashboard");
}
};
checkAuth();
}, [pathName, msalInstance]);
const storeRef = useRef<AppStore>();
if (!storeRef.current) {
storeRef.current = makeStore();
}
return (
<html lang="en">
<body>
<Provider store={storeRef.current}>
<PersistGate loading={null} persistor={storeRef.current.__persistor}>
<Box sx={{ display: "flex" }}>
<Grid container sx={{ backgroundColor: "#F8F8F9" }}>
<Grid item xs={true}>
{children}
</Grid>
</Grid>
</Box>
</PersistGate>
</Provider>
</body>
</html>
);
}
This is my handleLoginClick
functionality:
const handleLoginClick = async () => {
// msalInstance.loginRedirect();
try {
const loginRequest = {
scopes: ["openId", "profile", "User.Read"],
};
await msalInstance.loginRedirect(loginRequest);
const accountInfo = msalInstance.getAllAccounts()[0];
if (accountInfo) {
console.log("accountInfo", accountInfo);
const stateData = {
message: "Dashboard page",
loggedinUser: accountInfo,
};
dispatch(setDashboardData(stateData));
router.push("/dashboard");
}
} catch (error) {
console.error(error);
}
};
I believe this is issue with asynchronous behaviour with Next.js
. Not sure where to make changes for it to work. Can you help me with what is wrong in this code?
This indeed is asynchronous behaviours issue. in your layout.tsx
., you need to initialize
msalInstance before calling it. This will ensure msalInstance
is ready.
Here is the updated code:
const isAuthenticated = async () => {
await msalInstance.initialize(); // Add this line
const accounts = await msalInstance.getAllAccounts();
console.log("accounts", accounts);
return accounts.length > 0;
};