本文へ移動
verified onnext@16.3.5react@19
最終検証日 2026-09-26
Next.js道場

Next.js のログイン。認証をどこで確かめるか

Next.js のログインは、Cookie には鍵だけを入れ、誰なのかはページと Server Function がそれぞれサーバー側で確かめる形で作ります。

決めることは3つだけ

ログインの作り方はライブラリによって見た目が違いますが、App Router で決めることは3つです。

どれか1つでも崩れると、あとから足した守りが効かなくなる
決めること答え
Cookie に何を入れるか鍵だけ。名前や権限は入れない
Cookie をどこで書くか送信先(Server Function か Route Handler)
誰なのかをどこで確かめるか見せるページと、呼ばれる関数の両方

このサイトの最小構成のログインは、この3つをそのまま実装して動いています。説明用に書き直したコードではなく、そのページを動かしている本物のファイルです。

名前や権限を Cookie に入れると、利用者が自分で書き換えられます。Cookie はブラウザの持ち物だからです。

入れるのは、サーバー側の記録を引くための鍵だけにします。誰なのか、何ができるのかは、鍵を受け取ったサーバーが自分の記録を見て決めます。

越えるのは鍵だけ。中身はサーバーから出さない
サーバー側
鍵と持ち主の対応表
権限
有効期限
ブラウザ側
Cookie(鍵だけ)

越えられる: 鍵

越えられない: 名前・権限・メールアドレス

ページの中で書こうとすると、こうなります。実機で確かめました。

import { cookies } from 'next/headers'
 
export default async function Page() {
  const c = await cookies()
  c.set('session', 'abc')
  return <p>ログインしました</p>
}

ビルドは通ります。開いた瞬間に 500 になり、サーバーのログにこう出ます。

Error: Cookies can only be modified in a Server Action or Route Handler.

ページを描くのは表示のための処理です。開いただけで状態が変わると困るので、書き込みは送信を受け取る側に限られています。

ログインの流れ。書くのは送信先だけ
  1. Browser
    フォームを送る

    名前やメールアドレスを入れて送信する。

  2. Server
    送信先の Server Function

    確かめて、鍵を作り、Cookie に入れる。そして別のページへ移す。

  3. Server
    移った先のページ

    Cookie から鍵を読み、サーバー側の記録で誰かを確かめる。読むだけ。

守るのは、見せる側と呼ばれる側の両方

ここがいちばん抜けやすいところです。

ページは自分で確かめる

「ログインしていない人にはリンクを出さない」だけでは、URL を直接開かれたときに素通りします。見せたくないページは、そのページ自身が鍵を読んで確かめ、いなければ入口へ移します。

入口へ移す redirect を try で囲むと、移動せずに catch へ落ちます。取得だけを try で囲み、redirect は外で呼んでください(redirect が効かない)。

proxy で手前でも振り分けられますが、それは体験を良くするためのもので、守りではありません(proxy(旧 middleware)の書き方)。

Server Function も1つずつ確かめる

公式のドキュメントは、Server Action について画面を通らずに直接 POST で呼べると明記しています。そして、それぞれの中で認証と認可を確かめるように、と書いています。

画面にボタンが無いことは、守りにならない
画面で隠しているだけ
'use server'
export async function deleteItem(id: string) {
  await db.item.delete({ where: { id } })
}

ボタンを消しても、関数は外から呼べる。

呼ばれた側が確かめる
'use server'
export async function deleteItem(id: string) {
  const user = await currentUser()
  if (!user) throw new Error('forbidden')
  await db.item.delete({ where: { id, ownerId: user.id } })
}

誰が呼んだかを、関数の中で毎回確かめる。

ユーザー情報を丸ごと渡さない

Client Component に user をまるごと渡すと、画面に出していない項目もブラウザに届きます。実機で確かめると、名前しか表示していないのに passwordHash の値がブラウザに届く中身に入っていました(この問題)。

渡すのは、その部品が使う値だけにします。

<Greeting name={user.name} />   // user まるごとではなく

速くしようとして、ログインを壊さない

ログインしている人によって中身が変わるページに force-static を付けると、エラーも警告も出ないまま、ログインしていない状態の画面が全員に配られます。実機で確かめました(キャッシュの考え方)。

人によって変わるページは、アクセスのたびに描かれる側に置いたままにしてください。

まとめ

  1. Cookie には鍵だけ。中身はサーバーに置く
  2. Cookie を書けるのは送信先だけ。ページで書くと開いた時点で落ちる
  3. ページも Server Function も、それぞれ自分で確かめる。画面で隠すのは守りではない
  4. Client Component には使う値だけを渡す
  5. 人によって変わるページを静的にしない

出典

このページの内容は上記の一次情報を基準に書いています。個人ブログや二次情報を根拠にしていません。

理解できたか試す

では、このコードはどうなりますか。

ログインしたら Cookie に鍵を入れたいので、ページの中で set しました。ビルドは通りました。このページを開くとどうなりますか。

この問題を解くこのテーマの問題 3 問