The documentation for configuration of Azure B2C for next auth is incomplete. This post will serve as your guiding light, helping you seamlessly connect Azure B2C to your Next.js application.
If you’re new to creating an Azure B2C instance, NextAuth’s documentation provides excellent resources with links to the original Microsoft Documentation. In this article, we aim to provide a step-by-step guide on seamlessly configuring your Azure B2C instance with a Next.js application.
Following are the steps.
1. Create […nextauth].js file in the pages/api/auth folder and add the following configurations.
import NextAuth from "next-auth";
import AzureADB2CProvider from "next-auth/providers/azure-ad-b2c";
export default NextAuth({ providers: [ AzureADB2CProvider({ tenantId: process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME, clientId: process.env.NEXT_PUBLIC_AZURE_AD_B2C_CLIENT_ID, clientSecret: process.env.AZURE_AD_B2C_CLIENT_SECRET, primaryUserFlow: process.env.NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW, authorization: { params: { scope: "offline_access openid",
},
}, checks: ["pkce"], client: { token_endpoint_auth_method: "none",
}
}), // Add more providers here if needed ],
secret:process.env.NEXTAUTH_SECRET,
callbacks: {
async jwt({ token, account, profile }) {
if (account) {
token.accessToken = account.access_token
token.id = profile.id
}
return token
},
async session({ session, token, user }) {
session.accessToken = token.accessToken
session.user.id = token.id
return session
}
},
});
Since the client secret will be needed on the server side, there is no need to expose it to the public by adding NEXT_PUBLIC. If you need more information about the usage of these variables, whether it’s during build time or runtime, you can take a look at how the authentication process happens behind the scenes from the following URL.
https://next-auth.js.org/configuration/providers/oauth
If you have an endpoint to get the scope you can add that like below.
scope: "https://{}.onmicrosoft.com/{}/{}.read offline_access openid",
The secret attribute is not actually requires in local development.But it should be there in the production environment.
The callback functions in the provided code serve the purpose of retrieving user details, obtaining tokens for authorization, and managing user authentication within the frontend application.
2. Calling the Signin function
<a onClick={()=>{ signIn('azure-ad-b2c')}}>
Sign In
</a>
To ensure the desired behavior and prevent unnecessary redirection to the NextAuth prebuilt sign-in page, it is crucial not to pass a handleSignIn
function to the onClick
action. Instead, call the signIn
function with the provider's name directly from the JSX code, bypassing the default sign-in page. This approach allows you to effectively utilize other available providers without unnecessary intermediate function calls.
3. Signing Out
Next.js Documentation presents two distinct methods for signing out. The first method involves calling the /api/auth/signout
route, which triggers the sign-out process.
The second method involves utilizing the provided signOut
function provided by Next.js, which also facilitates the signing out process.
However, the problem arises when using these two methods. Although they clear the local session (NextAuth), they do not sign out the user from the Azure B2C instance. As a result, if the user attempts to sign in again, Azure B2C will send an error indicating to try again, as it recognizes that the user is still active and not properly signed out from the Azure B2C instance.
On this occasion, what we need to do is call the Azure B2C instance to sign out the user and then redirect to the /api/auth/signout page. From there, the user has to sign out again. The following code provides the complete code for signing out and signing in.
import { useEffect } from "react";
import { Avatar, Space } from "antd";
import { UserOutlined } from '@ant-design/icons';
import { useSession, signIn, signOut } from "next-auth/react";
const SIGN_OUT_URL=`https://${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.b2clogin.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_TENANT_NAME}.onmicrosoft.com/${process.env.NEXT_PUBLIC_AZURE_AD_B2C_PRIMARY_USER_FLOW}/oauth2/v2.0/logout?post_logout_redirect_uri=${process.env.NEXT_PUBLIC_POST_LOGOUT_REDIRECT_URI}`;
export default function NavUser() {
const { data: session, status } = useSession();
useEffect(() => {
if (status === "authenticated" && session) {
if (session) {
}
}
}, [session, status]);
const getUserName = () => {
return session && (
<Space>
<Avatar icon={<UserOutlined />} />
{session?.user?.name}
</Space>
);
};
return (
<>
{!session ? (
<a onClick={()=>{ signIn('azure-ad-b2c')}}>
Sign In
</a>
) : (
<>
<span>{getUserName()}</span>
<span >|</span>
<a style={{ color: 'blue' }} href={SIGN_OUT_URL}>
Sign Out
</a>
</>
)}
</>
);
}
When the signout button is pressed, it will trigger a call to the Azure B2C instance, signing out the user from there. Subsequently, the user will be redirected to the local signout page. However, to improve the user experience, it is recommended to bypass these prebuilt pages.
To accomplish this, you need to define the signout and signin pages in the configuration as follows:
pages: {
signOut: '/auth/signout',
signIn: '/auth/signin',
},
Next, create these pages in your Next.js app and add the following code:
import {signIn } from "next-auth/react";
export default function SignIn() {
if (typeof window !== 'undefined') {
signIn('azure-ad-b2c',{callbackUrl:"/"})
}
}
import {signOut } from "next-auth/react";
export default function Signout() {
if (typeof window !== 'undefined') {
signOut({ callbackUrl: "/" })
}
return null;
}
By implementing these custom signout and signin pages, you can ensure a smoother user experience. The signout page will trigger the signout function instead of showing us a signout UI again , and the signin page will initiate the signin process with Azure B2C, redirecting the user back to the desired location (in this case, the root page “/”) after authentication.
4. Authorization
To ensure proper authorization, it is crucial to intercept the token for each request. The following code snippet provides a solution to accomplish this task effectively:
import axios from 'axios';
import { getSession} from 'next-auth/react';
const instance = axios.create({
baseURL: process.env.NEXT_PUBLIC_API_BASE_URL,
});
instance.interceptors.request.use(
async (config:any) => {
const session = await getSession() as { accessToken?: string };
if (session) {
const accessToken = session.accessToken;
if (accessToken) {
config.headers = {
...config.headers,
Authorization: `Bearer ${accessToken}`
};
}
}
return config;
},
error => {
Promise.reject(error)
}
);
At the end, there is the MSAL library provided by Microsoft for authenticating React applications using Azure B2C. However, it currently lacks support for SSR (Server-Side Rendering). Therefore, the approach described in this article is the recommended way to configure authentication. Alternatively, if you are using other Identity and Access Management (IAM) solutions like AWS Cognito with amplify libary, they already provide built-in SSR support. Hope you found this article helpful.
Thank you, and cheers!