【Next.js】NextAuth→Auth.jsへの移行

## 記事の内容
先日作成した記事でNextAuthでの認証ロジックについて書きましたが、
NextAuth（V4）→Auth（V5）への移行を行ったので、
備忘録のために記事を書きます。
（移行が完璧かは不明ですが、とりあえず動いたので記事を作成）

■下記公式ドキュメントを参考にして、移行を実施しました。

https://authjs.dev/getting-started/migrating-to-v5

■以前書いたNextAuthの記事はこちらです。

https://qiita.com/H_Kagami_Gs/items/4b13f0a42395076ed569


## Auth.jsとNextAuth.jsの違い
- **NextAuth.js:**
Next.jsアプリケーション専用の認証ライブラリ
- **Auth.js:**
NextAuth.jsの後継で、Next.jsだけでなく
SvelteKitやSolidStartなど
他のフレームワークでも使える汎用認証ライブラリ

## 開発環境
| パッケージ | バージョン |
|:-:|:-:|
| Next.js  | 15.3.1  |
| NextAuth(Auth.js)  | 5.0.0-beta.28  |
| React  | 19.0.0  |

## 環境構築準備
- パッケージのインストール
`npm install next-auth@beta`  

## 実装例
::: note info
**20025/05/18更新**
- provided設定をAuth.js形式に変更
（ClientIDやSecretKeyをAuth.js指定の定義名に変更し、Auth.tsには環境変数の設定を削除）
- FacebookにallowDangerousEmailAccountLinkingの設定を追加
（これをつけないとなぜかVercelでエラーが発生する）
:::
#### Auth.js設定ファイル
```ts:app/lib/auth/auth.ts
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
import Facebook from "next-auth/providers/facebook"
import NextAuth from "next-auth"

export const { auth, handlers, signIn, signOut } = NextAuth({
    debug: process.env.NODE_ENV === 'development',
    providers: [
        GitHub,
        Google,
        Facebook({ allowDangerousEmailAccountLinking: true })
    ],
    callbacks: {
        authorized({ auth, request: { nextUrl } }) {
            // プロフィールページは認証済みユーザーのみアクセス可
            const isLogIn = !!auth?.user
            const isProfilePage = nextUrl.pathname.startsWith('/profile')
            if (isProfilePage) {
                return isLogIn
            }
            // 他のページは誰でもアクセス可
            return true
        },
        // ログイン時に作られる認証トークンにユーザーIDを追加
        jwt({ token, user }) {
            if (user) {
                token.id = user.id
            }
            return token
        },
        // ブラウザのセッションにユーザーIDを追加
        session: ({ session, token }) => {
            if (session.user && token.id) {
                session.user.id = token.id as string
            }
            return session
        },
        // ログイン・ログアウト後、HOME画面へリダイレクト
        redirect({ baseUrl }) {
            return baseUrl
        }
    },
    // 認証エラー用画面へ遷移
    pages: {
        error: '/auth/error'
    },
    // ユーザーのログイン状態を「JWT（JSON Web Token）」形式に指定
    session: {
        strategy: 'jwt'
    }
})
```
#### 認証APIルートの設定
```ts:app/api/auth/[...nextauth]/route.ts
import { handlers } from '@/app/lib/auth/auth'

export const { GET, POST } = handlers
```

#### 認証プロバイダーコンポーネント
```tsx:app/lib/auth/provider.tsx
"use client"

import { SessionProvider } from "next-auth/react"
import { FC, PropsWithChildren } from "react"

export const AuthProvider: FC<PropsWithChildren> = ({ children }) => {
    return <SessionProvider>{children}</SessionProvider>
}
```

#### レイアウトファイル
```tsx:app/layout.tsx
import type { Metadata } from "next"
import { Noto_Sans_JP } from "next/font/google"
import "./globals.css"
import Header from "./components/Header";
import { Suspense } from "react";
import LoadingSpinner from "./components/Loading";
import { AuthProvider } from "./lib/auth/provider";

const notoSansJP = Noto_Sans_JP({
  variable: "--font-noto-sans-jp",
  subsets: ["latin"],
  // 日本語フォントを使用するためにweight設定を追加
  weight: ["400", "500", "700"],
  // 必要に応じてpreloadを追加（日本語の場合は多くの文字があるため）
  preload: false
})

export const metadata: Metadata = {
  title: "Book Commerce",
  description: "Generated by create next app",
};

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <body
        className={`${notoSansJP.variable} antialiased`}
      >
          <AuthProvider>
            <Header />
            <div className="pt-20">
              <Suspense fallback={<LoadingSpinner />}>
                {children}
              </Suspense>
            </div>
          </AuthProvider>
      </body>
    </html>
  );
}
```

#### ログイン・ログアウトコンポーネント（例はヘッダー部コンポーネント）
```tsx:app/components/Header.tsx
import Image from "next/image"
import Link from "next/link"
import { auth } from "../lib/auth/auth"

const Header = async () => {

    const session = await auth()
    const user = session?.user

    return (
        <header className="bg-slate-700 text-gray-100 shadow-lg fixed top-0 left-0 right-0 z-30">
            <nav className="flex items-center justify-between p-4">
                <div className="flex items-center gap-2">
                    {user ? (
                        <Link
                            href={"/api/auth/signout"}  // Auth.jsが用意したログアウトの標準API
                            className="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium"
                        >
                            ログアウト
                        </Link>
                    ) : (
                        <Link
                            // href={"/login"}
                            href={"/api/auth/signin"}   // Auth.jsが用意したログインの標準API
                            className="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium"
                        >
                            ログイン
                        </Link>
                    )}
                </div>
            </nav>
        </header>
    )
}

export default Header
```

#### 環境変数
```env:.env
AUTH_GITHUB_ID=
AUTH_GITHUB_SECRET=

AUTH_GOOGLE_ID=
AUTH_GOOGLE_SECRET=

AUTH_FACEBOOK_ID=
AUTH_FACEBOOK_SECRET=
```

## まとめ
移行自体は割と簡単に進められました。
coreパッケージは未インストールのため、
純粋はAuth.jsの実装方法と異なる箇所がありますが、
とりあえず、V5での移行を進めてログイン・ログアウト認証は
正常動作したので、それまでの作業を残しておきます。

本記事が誰かの参考になれば、幸いです。