2023.10.05[Thu]

NextAuth.jsでAzure AD B2C認証のログアウトを実装する

  • Next.js

目次

  • - 環境
  • - 実装
  • - Azure AD B2Cのログアウト
  • - NextAuthのログアウト
  • - ログアウトの呼び出し方
  • - まとめ

こんにちは、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を利用している場合も同じような実装でログアウトすることができます。

参考
https://github.com/nextauthjs/next-auth/discussions/3938
https://learn.microsoft.com/ja-jp/azure/active-directory-b2c/openid-connect#send-a-sign-out-request

Share

ShopifyのカスタムLiquidの設定方法Shopifyコミュニティでの質問のコツ