【Next.js】NextAuth.jsでのログイン・ログアウト（Github、Google）

## 記事のきっかけ
- UdemyのNext.js講座でNextAuthを学んだ際の振り返りで理解を深めるため。

## NextAuth.jsとは
- NextAuth.jsは、Next.jsアプリケーションに
簡単に認証機能を追加できるライブラリです。
ログインやサインアップ、ユーザー管理などの複雑な認証処理を
少ないコードで実装できます。

### NextAuth.jsの主な特徴
1. **簡単セットアップ:** 数行のコードだけで認証システムを導入できます
2. **多様なログイン方法:** メール/パスワードだけでなく、
GoogleやGitHubなどのSNSログインも簡単に設定できます
3. **セキュリティ対策済み:** セキュリティの専門知識がなくても、
安全な認証システムが構築できます
4. **データベース不要:** シンプルな認証ならデータベースなしでも使えます

https://next-auth.js.org/

:::note info
- 現在、NextAuthの進化版としてAuth.jsがありますが、
講座ではNextAuthを使用していたのと、
Auth.jsの最新版がBetaとなっていて不安だったので、
講座の通り、NextAuthを使用しました。
:::
https://authjs.dev/

## 開発環境
| パッケージ名 | バージョン |
| --- | --- |
| Next.js | 15.3.1 |
| NextAuth | 4.24.11 |
| React | 19.0.0 |

## 環境構築
#### 1. Next.jsプロジェクト作成
`npx create-next-app@latest`

#### 2. 作成したプロジェクトのディレクトリ移動後、
NextAuthをインストール
`npm install next-auth `

#### 3. opnesslをインストール・秘密鍵作成
`brew install openssl `
※homebrewをインストールしていない方はインストールをお願いします。
`openssl rand -base64 32 `

### 4.Github側の準備（アプリケーション作成）
- GitHubにログインし、右上のプロフィールアイコン→「Settings」をクリック
左サイドバーから「Developer settings」を選択
- 「OAuth Apps」をクリック
- 「New OAuth App」ボタンをクリック
- 以下の情報を入力:
**Application name:** Book Commerce App（あなたのアプリ名）
**Homepage URL:** http://localhost:3000（開発環境）
**Application description:** 任意の説明
**Authorization callback URL:** http://localhost:3000/api/auth/callback/github
- 「Register application」をクリック
- 作成後、「Generate a new client secret」をクリックして、
**Client Secret**を発行
- **Client ID**と**Client Secret**をメモしておく（環境変数として使用）

### 5.Github側の準備（Google Cloud Platform設定）
- **Google Cloud Console** にアクセス・ログイン
- 画面上部の「プロジェクトを選択」→「新しいプロジェクト」をクリック
- プロジェクト名を入力し「作成」をクリック
- 左サイドメニューから「APIとサービス」→「認証情報」を選択
- 「認証情報を作成」→「OAuth クライアントID」をクリック
- 「同意画面を構成」をクリック
**User Type:** 外部を選択
必要な情報（アプリ名、メールアドレスなど）を入力
スコープは基本的なものだけ選択
テストユーザーに自分のメールアドレスを追加
- 「認証情報を作成」→「OAuth クライアントID」を再度クリック
以下の情報を入力:
**アプリケーションの種類:** ウェブアプリケーション
**名前:** 自分のアプリ名
**承認済みのリダイレクトURI:** http://localhost:3000/api/auth/callback/google
- 「作成」をクリック
- クライアントIDとクライアントシークレットをメモしておく


## 実装例
■環境変数（.env）
```env:.env
NEXTAUTH_SECRET=opensslの秘密鍵
GITHUB_ID=GithubのアプリID
GITHUB_SECRET=Githubのシークレットキー

GOOGLE_CLIENT_ID=GoogleのクライアントID
GOOGLE_CLIENT_SECRET=Githubのシークレットキー
```

■NextAuthの認証設定
```ts:app/lib/next-auth/option.ts
import { NextAuthOptions } from "next-auth"
import GithubProvider from "next-auth/providers/github"
import GoogleProvider from "next-auth/providers/google"

export const nextAuthOptions: NextAuthOptions = {
    debug: false,
    providers: [
        GithubProvider({
            clientId: process.env.GITHUB_ID!,
            clientSecret: process.env.GITHUB_SECRET!
        }),
        GoogleProvider({
            clientId: process.env.GOOGLE_CLIENT_ID!,
            clientSecret: process.env.GOOGLE_CLIENT_SECRET!
        })
    ],
    callbacks: {
        session: ({ session, token, user }) => {
            return {
                ...session,
                ...token,
                user: {
                    ...session.user,
                    ...token,
                    id: user.id
                }
            }
        }
    },
    secret: process.env.NEXTAUTH_SECRET
}
```

■認証情報の共有設定
```tsx:app/lib/next-auth/provider.tsx
"use client"

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

export const NextAuthProvider: 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 { NextAuthProvider } from "./lib/next-auth/provider";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <html lang="ja">
      <body
        className={`${notoSansJP.variable} antialiased`}
      >
        <NextAuthProvider>
            {children}
        </NextAuthProvider>
      </body>
    </html>
  );
}
```

■ログイン認証APIルート設定
※[...nextauth]はNext.jsの「API Route」機能を使う際に必要です。
```ts:app/api/[...nextauth]/route.ts
import { nextAuthOptions } from "@/app/lib/next-auth/options";
import NextAuth from "next-auth";

const handler = NextAuth(nextAuthOptions)

export { handler as GET, handler as POST }
```

■ログイン画面へ遷移するためのリンク設定
※href指定の「"/api/auth/signin"」は
　NextAuthで用意されている標準サインイン画面に遷移します。
```tsx:app/sample/page.tsx
import Link from "next/link"

// 〜ソース抜粋〜
<Link
    href={"/api/auth/signin"}   // Next-Authが用意したログインの標準API
    className="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium"
>
    ログイン
</Link>
```

■ログアウト画面へ遷移するためのリンク設定
```tsx:app/sample/page.tsx
import Link from "next/link"

// 〜ソース抜粋〜
<Link
    href={"/api/auth/signout"}   // Next-Authが用意したログアウトの標準API
    className="text-gray-300 hover:text-white px-3 py-2 rounded-md text-sm font-medium"
>
    ログアウト
</Link>
```

■ログイン画面
![スクリーンショット 2025-05-10 13.21.55.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/09113f82-5a49-4194-92a7-fca1b2747b97.png)

■ログアウト画面
![スクリーンショット 2025-05-10 13.24.06.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/d0f0ba36-b335-4a44-a8c9-460d68e92ad9.png)

## おまけ（プロフィール画像）
Githubやgoogleのプロフィール画像を表示する場合は、
next.config.tsでドメイン指定が必要になります。
※指定しないとエラーが発生します。
```ts:next.config.ts
import type { NextConfig } from "next";

const nextConfig: NextConfig = {
  /* config options here */
  images: {
    domains: [
      "avatars.githubusercontent.com",  // Githubのプロフィール画像
      "lh3.googleusercontent.com"  // Googleのプロフィール画像
    ]
  }
};

export default nextConfig;
```

プロフィール画像表示画面（ヘッダー用コンポーネント）
```tsx:app/components/Header.tsx
import { getServerSession } from "next-auth"
import Image from "next/image"
import Link from "next/link"
import { nextAuthOptions } from "../lib/next-auth/options"

const Header = async () => {

    const session = await getServerSession(nextAuthOptions)
    const user = session?.user

    return (
        <header className="bg-slate-700 text-gray-100 shadow-lg">
            <nav className="flex items-center justify-between p-4">
                <div className="flex items-center gap-2">
                    <Link href={"/profile"}>
                        <Image
                            width={50}
                            height={50}
                            alt="profile_icon"
                            src={user?.image || "/default_icon.png"}
                            className="rounded-full"
                        />
                    </Link>
                </div>
            </nav>
        </header>
    )
}

export default Header
```

#### ログイン前
![スクリーンショット 2025-05-10 19.24.02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/2890bf0b-1aa9-410c-bda2-e15e735943b9.png)

#### ログイン後（画像はGithubのプロフィール画像です）
![スクリーンショット 2025-05-10 19.30.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3983478/d0b7a2e5-c49a-437f-a5d0-7c4fc5af207d.png)

## まとめ
X（旧Twitter）とFacebookも挑戦したのですが、
Facebookは本番環境ではエラーで解決できず、
Xは認証自体お金がかかりそうだったので、一旦諦めました。
やり方がわかる方いらっしゃいましたら、コメントいただけると嬉しいです。

今度はリファクタリングでAuth.jsを使っての認証を試してみたいです。