Next.jsでログイン機能を実装|NextAuth.jsでID・パスワード認証対応

Next.js + NextAuth.js でログイン認証を実装方法 API
Next.js + NextAuth.js でログイン認証を実装方法
この記事は約23分で読めます。

でじぼうです。

Next.js アプリにログイン機能を実装したい方向けの記事です。

でじぼう
でじぼう

この記事は下記の方がおすすめ!

  • Next.js でログイン機能を実装したい
  • 全体構成がわからない
  • 実装の注意点を知りたい

Instagramフォロワー数9,500人を越える人気のもち・大福店 えにかいたもち

全体構成

Next.jsでログイン機能を実装|NextAuth.jsでID・パスワード認証対応

認証設定(lib/auth.ts)

Next.js でログイン機能を作るときに必要な「設定ファイル」です。
NextAuth.js というライブラリが、このファイルを見てどう動くか決めます。

NextAuthの設定内容

ログイン方式の設定(providers)

「どんな方法でログインできるようにするか」を決める場所です。
たとえば

  • ユーザー名とパスワードでログイン → CredentialsProvider を使う
  • Google アカウントでログイン → GoogleProvider を使う
  • GitHub アカウントでログイン → GitHubProvider を使う

今回は「IDとパスワードでログインしたい」ので CredentialsProvider を使います。

セッション管理(session)

「ログイン状態(セッション)をどうやって覚えておくか?」を設定します。
よく使われる方法は2つ

  • jwt:サーバーに保存せず、ログイン情報をクッキーに入れて管理(軽い)
  • database:ログイン情報をデータベースに保存(管理がしっかり)

今回は簡単な構成にしたいので、jwt を使っています。

コールバック関数(callbacks)

「ログインしたあと、追加で何かしたいとき」に使います。
たとえば

  • ログインしたユーザーの「名前」や「権限(adminなど)」をセッションに保存する
  • トークンに extra 情報を加える

今回は、「管理者かどうか(role)」をセッションに入れています。

lib 配下にファイルを配置する理由

lib は「ロジック(処理)や設定などの共通ユーティリティを置く場所」という意味で使われます。auth.tsアプリ全体で使う「認証の設定」 をまとめたものなので、lib に置くのが自然です。

🗂 配置イメージ

ディレクトリ役割
lib/アプリ全体で使い回すロジックや設定の置き場
app/api/  もしくは     pages/api/APIルート(APIエンドポイント)
components/UIの部品化されたパーツの置き場

実装内容

⚠️ 実装時の注意点

  • ログインID・パスワードは直書き厳禁。今回は.envに直書きしてしまっています。
    (次回、Azure SQL Databaseにパスワード等を格納する記事出します!)
  • role など独自の情報をセッションやトークンに保持したい場合は、callbacks を使って明示的に追加します。
import CredentialsProvider from "next-auth/providers/credentials";
import type { Session } from "next-auth";
import type { JWT } from "next-auth/jwt";

interface Token extends JWT {
  role?: string;
}

export const authOptions = {
  // CredentialsProvider の設定(IDとパスワードでログインさせるための仕組み)
  providers: [
    CredentialsProvider({
      // ログイン画面のフォームの中身
      credentials: {
        username: { label: "ユーザーID", type: "text" },
        password: { label: "パスワード", type: "password" },
      },

      // 入力されたIDとパスワードが正しいかチェックする関数
      async authorize(credentials) {
        if (
          credentials?.username === process.env.["任意の環境変数名"] &&
          credentials?.password === process.env.["任意の環境変数名"]
        ) {
          return { id: "1", name: "管理者", role: "admin" };
        }
        return null;
      },
    }),
  ],
  // 独自に作成するページのパス
  pages: {
    signIn: "/auth/signin",
  },
  // セッションの設定
  session: {
    strategy: "jwt" as const,
    maxAge: 100,
  },
  // ログイン後、ユーザー情報(user)やトークン(token)に追加情報(ここでは role)を保持・共有
  callbacks: {
    // useSession() で取り出すとき、session.user にも role を渡す
    async session({ session, token }: { session: Session; token: Token }) {
      session.user = {
        ...session.user,
        role: token.role ?? null,
      } as typeof session.user & { role?: string | null };
      return session;
    },
    // ログイン後、トークンに role を保存
    async jwt({ token, user }: { token: Token; user?: unknown }) {
      if (user && typeof user === "object" && "role" in user) {
        token.role = (user as { role?: string }).role;
      }
      return token;
    },
  },
};

認証エンドポイント(app/api/auth/[...nextauth]/route.ts)

APIルートの設定です。NextAuth の設定を使ってログイン処理などを実行できるようにします。

⚠️ 実装時の注意点

  • route.ts では NextAuth をラップし、GET/POST 両方のメソッドをエクスポートする必要があります。
  • authOptions を正しく lib/auth.ts からインポートできていることを確認してください。
import NextAuth from "next-auth/next";
import { authOptions } from "@/lib/auth";

const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

アプリ全体で認証を使う(components/Providers.tsx)(app/layout.tsx)

セッション(ログイン状態)をアプリ全体で共有できるようにするためのコンポーネントです。

アプリのレイアウト全体を Providers で囲み、どのページでもログイン情報を使えるようにします。

SessionProviderとは

SessionProvider は、ログイン状態(セッション)をアプリ全体で使えるようにするための部品です。

囲んでいないと、
useSession()(ログイン状態を取得する関数)
getSession()(セッション情報を取得する関数)
などが正しく動作しません。

SessionProvider を直接 layout.tsxに記載しない理由

Next.js App Router では layout.tsx はサーバーコンポーネントであることが前提 です。なので、以下のような理由で SessionProvider を直接 layout.tsx に書けないことがあります。

❗問題の背景

  • SessionProviderクライアントコンポーネント("use client" が必要)
  • 一方で、layout.tsx はデフォルトで サーバーコンポーネント
  • サーバーコンポーネントに use client をつけると、metadata などのサーバー専用機能が使えなくなる

SessionProvider を直接 layout.tsx に書くと、layout.tsx 全体がクライアントコンポーネントになり、Next.js 本来の機能(metadata など)が使えなくなる。

そのため、別のクライアントコンポーネントに切り出してラップするのが推奨されているわけです。

実装内容

✅ 解決策:<Providers> コンポーネントを別に作成して、ラップする!!

Providers.tsx を作って "use client" をつけることで、

"use client";
import { SessionProvider } from "next-auth/react";

export function Providers({ children }: { children: React.ReactNode }) {
  return <SessionProvider>{children}</SessionProvider>;
}

これを layout.tsx 側で下記のようにラップする。

import type { Metadata } from "next";
import { Providers } from "@/components/Providers";
import "./globals.css";

export const metadata: Metadata = {
  title: "Create Next App",
  description: "Generated by create next app",
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        <Providers>{children}</Providers>
      </body>
    </html>
  );
}

ログインフォーム(app/auth/signin/page.tsx)

ユーザーがログインIDとパスワードを入力、ログイン処理を実行するフォームです。

⚠️ 実装時の注意点

  • signIn("credentials") の引数 redirect: false を忘れると、自動遷移してしまいエラー処理が難しくなります。
"use client";
import { useState } from "react";
import { signIn } from "next-auth/react";

export default function SignInForm() {
  const [userId, setUserId] = useState("");
  const [password, setPassword] = useState("");
  const [errorMsg, setErrorMsg] = useState("");

  const handleSubmit = async (e: React.FormEvent) => {
    e.preventDefault();
    setErrorMsg("");

    const res = await signIn("credentials", {
      redirect: false,
      username: userId,
      password,
    });

    if (res?.error) {
      setErrorMsg("ログインIDまたはパスワードが間違っています");
    } else if (res?.ok) {
      window.location.href = "/download";
    } else {
      setErrorMsg("不明なエラーが発生しました");
    }
  };

  return (
    <div className="flex items-center justify-center min-h-screen bg-cover bg-center" style={{ backgroundImage: "url('/images/white-background.png')" }}>
      <form onSubmit={handleSubmit} className="bg-white p-8 rounded shadow-md w-[500px] mx-auto">
        <h1 className="text-2xl font-bold mb-6 text-center">ログイン</h1>

        <label className="block mb-4">
          <span className="block text-gray-700 font-semibold mb-1">ログインID</span>
          <input type="text" value={userId} onChange={(e) => setUserId(e.target.value)} required className="w-full px-3 py-2 border border-gray-300 rounded" />
        </label>

        <label className="block mb-4">
          <span className="block text-gray-700 font-semibold mb-1">パスワード</span>
          <input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required className="w-full px-3 py-2 border border-gray-300 rounded" />
        </label>

        {errorMsg && <p className="text-red-600 text-center mb-4">{errorMsg}</p>}

        <button type="submit" className="w-full bg-blue-600 text-white py-2 rounded hover:bg-blue-700">ログイン</button>
      </form>
    </div>
  );
}

未ログイン時のログイン誘導(components/Login.tsx)

ユーザーが未ログインだった場合に、ログインボタンを表示してログイン画面に誘導する処理です。

⚠️ 実装時の注意点

  • useSession() を使うには、呼び出し元が SessionProvider 配下である必要があります。
"use client";

import { signIn } from "next-auth/react";

export default function Login() {
  return (
    <div className="mt-8 text-center">
      <button
        className="text-blue-600 underline"
        onClick={() => signIn(undefined, { callbackUrl: "/auth/signin" })}
      >
        🔒 管理者ログインはこちら
      </button>
    </div>
  );
}

実行画面

下記のリンク押下で、ログイン画面が表示される

Next.jsでログイン機能を実装|NextAuth.jsでID・パスワード認証対応

ログインID・パスワードを入力

Next.jsでログイン機能を実装|NextAuth.jsでID・パスワード認証対応

ログイン完了したら、管理者画面が表示される(この画面のコードはなし)

Next.jsでログイン機能を実装|NextAuth.jsでID・パスワード認証対応

まとめ

Next.js + NextAuth.js を使えば、

  • ID・パスワードでのログイン認証(Credentials)
  • JWTベースのセッション管理
  • セッション情報(ユーザー情報)をアプリ全体で使える

という構成が簡単に作れます。

これをベースに、管理者権限のアクセス制限や、ログアウト処理も簡単に追加できます。

「最低限の認証機能を実装したい」という方は、この記事の構成をそのまま使えばOKです!

コメント