はじめに
NextAuth.jsでAzure AD (Azure Active Directory) をプロバイダとして使用する際に、profile情報にいくつか値を追加したかったのですが、やり方が分からず困ったので、調べたこととやり方を記載します。
NextAuth.jsの説明やAzure ADをプロバイダとして利用する方法などはこの記事では詳しく書きません。詳しくはドキュメントを参照ください。
環境
Next.js: v13.4.9
NextAuth.js: v4.22.1
profile情報を追加する
まずAzure ADをプロバイダとしてセットアップすると以下のようになっているかと思います。
import NextAuth from 'next-auth'
import AzureADProvider from "next-auth/providers/azure-ad";
export default NextAuth({
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
    })
  ],
  // その他の設定...
})
デフォルトではname email imageをprofile情報として受け取っているようです。
ただしemailはAzure Adのメールプロパティになるので、注意が必要です。メールアドレスは必須ではないですが、upn (User Principal Name)をメールアドレスとして利用したかったので、編集していきます。
profileオプションをカスタマイズしてupnを返すようにする
export default NextAuth({
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      async (profile) => {
        return {
          id: profile.id,
          name: profile.name,
          email: profile.preferred_username,  // upnをメールアドレスとして利用
        }
      }
    })
  ],
  // その他の設定...
})
この profile 関数は、Azure AD から返されるプロファイル情報を受け取り、next-auth.js が期待する形式のプロファイルオブジェクトを返します。
ただ profile 情報を確認したところupnという値はなく、preferred_nameというプロパティがあり、こちらにupnの値が入っていました。Azure AD において、preferred_usernameは通常、ユーザーのupnを指すようなので、こちらをメールアドレスとして利用します。
ただ profile オブジェクトをカスタマイズしたので、アバター画像の取得ができなくなってしまいました。画像は同様に取得したかったので、デフォルトの profile オプションがどうなっているか調べてみました。
どうやら画像は graph API で別途取得しているようです。
https://github.com/nextauthjs/next-auth/blob/main/packages/core/src/providers/azure-ad.ts
デフォルトの実装をそのままコピペしてきます。
export default NextAuth({
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      async profile(profile, tokens) {
          // https://docs.microsoft.com/en-us/graph/api/profilephoto-get?view=graph-rest-1.0#examples
          const response = await fetch(
            `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
            { headers: { Authorization: `Bearer ${tokens.access_token}` } }
          )
    
          // Confirm that profile photo was returned
          let image
          // TODO: Do this without Buffer
          if (response.ok && typeof Buffer !== "undefined") {
            try {
              const pictureBuffer = await response.arrayBuffer()
              const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
              image = `data:image/jpeg;base64, ${pictureBase64}`
            } catch {}
          }
    
          return {
            id: profile.sub,
            name: profile.name,
            email: profile.preferred_username,  // upnをメールアドレスとして利用
            image: image ?? null,
          }
      },
    },
  ],
  // その他の設定...
})
必要なプロパティを追加する
もともとやりたかったこととして従業員IDを取得したかったので、そちらを実装していきます。
export default NextAuth({
  providers: [
    AzureADProvider({
      clientId: process.env.AZURE_AD_CLIENT_ID,
      clientSecret: process.env.AZURE_AD_CLIENT_SECRET,
      tenantId: process.env.AZURE_AD_TENANT_ID,
      async profile(profile, tokens) {
        const [imageResponse, employeeIdResponse] = await Promise.all([
          fetch(
            `https://graph.microsoft.com/v1.0/me/photos/${profilePhotoSize}x${profilePhotoSize}/$value`,
            { headers: { Authorization: `Bearer ${tokens.access_token}` } }
          ),
          fetch(
            `https://graph.microsoft.com/v1.0/me>$select=employeeId`,
            { headers: { Authorization: `Bearer ${tokens.access_token}` } }
          )
        ])
    
        // Confirm that profile photo was returned
        let image
        // TODO: Do this without Buffer
        if (imageResponse.ok && typeof Buffer !== "undefined") {
          try {
            const pictureBuffer = await imageResponse.arrayBuffer()
            const pictureBase64 = Buffer.from(pictureBuffer).toString("base64")
            image = `data:image/jpeg;base64, ${pictureBase64}`
          } catch {}
        }
        const { employeeId } = await employeeIdResponse.json()
    
        return {
          id: profile.sub,
          name: profile.name,
          email: profile.preferred_username,  // upnをメールアドレスとして利用
          image: image ?? null,
          employeeId
        }
      },
    },
  ],
  // その他の設定...
})
できました。
TypeScriptを利用していて、プロパティを追加する場合は型の修正も必要になるかと思います。必要に応じてnext-auth.d.tsも修正してください。