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

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

※当サイトはアフィリエイト広告を利用しています。商品リンクにはプロモーションを含む場合があります。

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

困ってた自分に届けたい話

初めてログイン機能を作ったとき、「ログイン状態をどうやって保持すればいいの?」「セッションって何?」と疑問だらけ。

ググって出てきたコードをコピペしてみても、うまく動かず。
認証の仕組みが見えづらくて、「これで本当に安全なのかな?」と不安になることも…。

そんなときに出会ったのが NextAuth.js

てんハロ運営者
てんハロ運営者

この記事は、同じように困っていた方への備忘録兼シェアとして書いています。

Next.js をもっと詳しく学びたい人へおすすめの本

▶ 動かしながら基礎から学びたい人に
「まずは手を動かしたい!」という人にぴったりの一冊。基本構造から丁寧に解説されており、Next.jsとReactの連携も自然に身につきます。初学者でもステップを追いやすく、サンプルアプリの構築を通して理解を深められる構成です。

動かして学ぶ!Next.js/React開発入門

新品価格
¥3,168から
(2025/7/21 23:18時点)

▶ Reactの経験があり、次のステップへ進みたい人に
「Reactは少し触ったことがあるけど、Next.jsを仕事で使いこなしたい」という人向け。実践を意識した構成で、Webアプリの構造、レンダリング戦略、SEO対応など、現場で必要なノウハウが詰まっています。

React Next.js 実践プログラミング入門: React Next.js モダンWeb開発実践ガイド

新品価格
¥3,300から
(2025/7/21 23:17時点)

▶ App Routerを本格的に学びたいエンジニアへ
App Routerを軸に、最新のNext.js開発に対応した本格派。中級者以上の方が「一歩先へ進みたい」と感じたときに読みたい一冊。デザインパターンや設計の視点もあり、長期的に役立つ内容です。

実践Next.js —— App Routerで進化するWebアプリ開発 エンジニア選書

新品価格
¥3,665から
(2025/7/21 23:18時点)

全体構成

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

認証設定(lib/auth.ts)

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

NextAuthの設定内容

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

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

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

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

セッション管理(session)

「ログイン状態(セッション)をどうやって覚えておくか?」を設定します。

  • 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・パスワード認証対応
てんハロ運営者
てんハロ運営者

おつかれさまでした!

更新をF5連打で待つの、そろそろやめませんか?
( ブログ更新をメールでそっとお知らせします🙇‍♂️ )

スパムはしません!詳細については、プライバシーポリシーをご覧ください。

コメント