I am working on a React web application which is using React 18
and Next.js 14
. I am using App Router
as recommended by Next.js boiler plate set-up which creates app
folder inside src
folder. Is using layout.tsx
as entry point for my application.
I do not have pages
folder or _app.tsx
as mentioned in many articles.
I have gone through official documentaion of next.js ( here ). But no luck in finding clear detail about configuring Azure SSO
with App Router
for my Next.js
app.
Just wondering if any article available which explains the flow in detail. Or working example with above prerequsites?
App Router
can be little tricky when handling redirection while implementing SSO
. I will guide you through step by step to configure next-auth
to your React 18
, next.js 14
and App Router
.
step 1: Install dependencies
next-auth
using npm i next-auth
commandstep 2: Setting up Path for next-auth
configuration
App Router
., You must see app
folder inside src
Next.js 14
., As per official documentation of [next-auth][1]
., configuration has been made compatible for App Router
as followsapi
folder inside app
folderauth
folder inside api
[...nextauth]
folder inside auth
folderroute.ts
file inside [...nextauth]
foldersrc/app/api/auth/[..nextauth]/router.ts
step 3: Setting up next-auth configuration
https://localhost:3000/api/auth/callback/azure-ad
in Azure App
registration redirect URL.https://yourapplication.com/api/auth/callback/azure-ad
in Azure App registration redirect URL.router.ts
. Copy below code and customize to your requirementsimport NextAuth from "next-auth";
import AzureADProvider from "next-auth/providers/azure-ad";
const { AZURE_AD_CLIENT_ID, AZURE_AD_CLIENT_SECRET, AZURE_AD_TENANT_ID } =
process.env;
if (!AZURE_AD_CLIENT_ID || !AZURE_AD_CLIENT_SECRET || !AZURE_AD_TENANT_ID) {
throw new Error("The Azure AD environment variables are not set.");
}
const handler = NextAuth({
secret: AZURE_AD_CLIENT_SECRET,
providers: [
AzureADProvider({
clientId: AZURE_AD_CLIENT_ID,
clientSecret: AZURE_AD_CLIENT_SECRET,
tenantId: AZURE_AD_TENANT_ID,
}),
],
callbacks: {
async jwt({ token, account }) {
if (account) {
token = Object.assign({}, token, {
access_token: account.access_token,
});
}
return token;
},
async session({ session, token }) {
if (session) {
session = Object.assign({}, session, {
access_token: token.access_token,
});
console.log(session);
}
return session;
},
},
});
export { handler as GET, handler as POST };
Note: You can decode
access_token
andid_token
to fetchgroups
,token_expiry
etc with help of jwt-decode subject to your requirements.
Step 4: Setting up SessionProvider
layout.tsx
and wrap your app inside SessionProvider
as follows"use client";
import React, { useRef } from "react";
import "./globals.css";
import { Box } from "@mui/material";
import { AppStore, makeStore } from "@/lib/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { SessionProvider } from "next-auth/react";
import { ProtectedComponents } from "./components/ProtectedComponents";
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const storeRef = useRef<AppStore>();
if (!storeRef.current) {
storeRef.current = makeStore();
}
return (
<html lang="en">
<body>
<SessionProvider>
<Provider store={storeRef.current}>
<PersistGate
loading={null}
persistor={storeRef.current.__persistor}
>
<Box sx={{ display: "flex" }}>
<ProtectedComponents>{children}</ProtectedComponents>
</Box>
</PersistGate>
</Provider>
</SessionProvider>
</body>
</html>
);
}
Note: Do not worry about importing
route.js
code.,next-auth
automatically picks configuration if placed in right place
In above code., I have mentioned ProtectedComponents
to access subject to session
availability.
Lets try to set ProtectedComponents
by picking session
information as follows.
import React, { useEffect, ReactNode } from "react";
import { useSession } from "next-auth/react";
import { usePathname, useRouter } from "next/navigation";
import { Grid } from "@mui/material";
import Header from "./Header";
export const ProtectedComponents = ({ children }: { children: ReactNode }) => {
const { data: session, status, update } = useSession();
const router = useRouter();
const pathName = usePathname();
useEffect(() => {
if (status === "loading") return; // Do nothing while loading
if (session && pathName === "/") router.push("/dashboard");
if (!session) router.push("/"); // If not authenticated, force log in
}, [session, status]);
return (
<Grid container sx={{ backgroundColor: "#F8F8F9" }}>
<Grid item xs={true}>
<Grid container>
{session && pathName !== "/" && (
<Grid item xs={12}>
<Header />
</Grid>
)}
<Grid item p={2} xs={true}>
{children}
</Grid>
</Grid>
</Grid>
</Grid>
);
};
Step 5: Setting up login and logout methods.
signIn
and signOut
by next-auth
as followsfor login:
import { signIn } from "next-auth/react";
const handleLoginClick = async () => {
try {
signIn();
} catch (error) {
console.error(error);
}
};
<Button
variant="contained"
color="primary"
fullWidth
onClick={handleLoginClick}
>
Login
</Button>;
For logout:
import { signOut } from "next-auth/react";
const handleLogOutClick = async () => {
try {
signOut();
} catch (error) {
console.error(error);
}
};
<Button
variant="contained"
color="primary"
fullWidth
onClick={handleLogOutClick}
>
Login
</Button>;
This basically concludes the initial setup required for next-auth configuration to do Azure single sign on
.
Step 6: Setting NEXTAUTH_URL
.
Last but not least. NEXTAUTH_URL
is key for next-auth
service to capture your redirection url
post authentication.
if not specified., it default redirects to localhost
.
As long as you are working in localhost., NEXTAUTH_URL
is not mandatory. But when deploying to environments like dev, qa and prod., it is must to define NEXTAUTH_URL
in .env
.
it will be something like this NEXTAUTH_URL=https://myapp.com
I suppose this removes your blocker.