NextAuth.jsでAzure AD認証する際にprofile情報を追加したい

## はじめに
NextAuth.jsでAzure AD (Azure Active Directory) をプロバイダとして使用する際に、profile情報にいくつか値を追加したかったのですが、やり方が分からず困ったので、調べたこととやり方を記載します。
NextAuth.jsの説明や[Azure ADをプロバイダとして利用する方法](https://next-auth.js.org/providers/azure-ad)などはこの記事では詳しく書きません。詳しくはドキュメントを参照ください。

## 環境
Next.js: v13.4.9
NextAuth.js: v4.22.1

## profile情報を追加する

まずAzure ADをプロバイダとしてセットアップすると以下のようになっているかと思います。

```pages/api/auth/[...nextauth].js
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を返すようにする

```pages/api/auth/[...nextauth].js
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

デフォルトの実装をそのままコピペしてきます。

```pages/api/auth/[...nextauth].js
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を取得したかったので、そちらを実装していきます。

```pages/api/auth/[...nextauth].js
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`も修正してください。