こんにちは、Webエンジニアの永井です。
NextAuth でAzure AD B2C認証のログアウトを実装するのに苦労したので記事にします。
NextAuth にはログアウト用のsignOut関数が用意されていますが、ローカルセッションを削除するだけで、Azure AD B2Cのセッションは削除されません。そのため、B2Cで再度ログインしようとするとログイン情報を求められずに自動的にログインされてしまいます。
この問題を解決するための方法を紹介します。
環境
Next.js:13.4.7
NextAuth.js:4.22.1
実装
Azure AD B2Cのログアウト
B2Cからログアウトするには、B2CのログアウトURLにリダイレクトする必要があります。ログアウトURLは次のようになっています。
https://{process.env.TENANT_NAME}.b2clogin.com/${process.env.TENANT_NAME}.onmicrosoft.com/${process.env.USER_FLOW}/oauth2/v2.0/logout?post_logout_redirect_uri=${process.env.NEXTAUTH_URL}/auth/signout&id_token_hint=${idToken}
クエリパラメータのpost_logout_redirect_uri=${process.env.NEXTAUTH_URL}/auth/signout
は、NextAuthのログアウトを実装するために設定しています。NextAuthのsignOut
はクライアント上でしか呼び出せないので、リダイレクト後にクライアント側で実行します。
id_token_hint
は、post_logout_redirect_uri
のURLがB2Cに登録済みのURLと一致するか検証するのに利用されます。一致しない場合はリダイレクトされないので、セキュリティで保護することができます。id_token
はログイン時にNextAuthのコールバックから取得できるので、セッションに保存して使いまわせるようにします。
// /@types/next-auth.d.ts
declare module 'next-auth' {
interface Session {
idToken?: string;
}
}
declare module 'next-auth/jwt' {
interface JWT {
idToken?: string;
}
}
// /api/auth/[...nextauth].ts
import NextAuth, { NextAuthOptions } from 'next-auth';
...
export const authOptions: NextAuthOptions = {
...
callbacks: {
jwt: async ({ account, token }) => {
if (account) {
token.idToken = account.id_token;
}
return token;
},
session: ({ session, token }) => {
session.idToken = token.idToken;
return session;
},
},
};
export default NextAuth(authOptions);
B2CのログアウトURLにはenvの内容を利用するので、バックエンドにAPIとして実装します。
// /api/auth/federated-logout.ts
import type { NextApiRequest, NextApiResponse } from 'next';
import { getServerSession } from 'next-auth';
import { authOptions } from './[...nextauth]';
const federatedLogout = async (req: NextApiRequest, res: NextApiResponse) => {
try {
const session = await getServerSession(req, res, authOptions);
const token = session?.token;
if (!token) throw new Error('id_token is undefined.');
const logoutUrl = `https://${process.env.TENANT_NAME}.b2clogin.com/${process.env.TENANT_NAME}.onmicrosoft.com/${process.env.USER_FLOW}/oauth2/v2.0/logout?post_logout_redirect_uri=${process.env.NEXTAUTH_URL}/auth/signout&id_token_hint=${token}`
res.redirect(logoutUrl);
} catch (error) {
console.error(error);
}
};
export default federatedLogout;
NextAuthのログアウト
B2Cのログアウト後にリダイレクトされる画面を実装して、NextAuthのログアウト処理を書きます。今回の場合、post_logout_redirect_uri=${process.env.NEXTAUTH_URL}/auth/signout
だったので、/auth/signout
の画面を実装します。
// /pages/auth/signout.tsx
import { signOut } from 'next-auth/react';
const Signout = () => {
if (typeof window !== 'undefined') {
signOut();
}
return null;
};
export default Signout;
ログアウトの呼び出し方
ログアウトページなどから、次のように呼び出します。
<button onClick={() => window.location.href = "/api/auth/federated-logout"}>
Sign out
</button>
まとめ
セッションが切れずに困っている方の参考になれば幸いです。
今回はAzure AD B2Cを例にしましたが、他のOpenID Connectを利用している場合も同じような実装でログアウトすることができます。