Next.js で認証を実装するいちばん手堅い方法は、認証ライブラリ(Better Auth など)に登録・ログイン・セッション管理を任せ、proxy.ts では Cookie の有無だけを確認し、ページと Server Action の中で本当のセッション検証をする構成です。 Next.js 公式の認証ガイドもこの「入口は楽観的チェック、データの近くで確実なチェック」という考え方を推奨しています。

この記事では、Next.js 16.3.6・Better Auth 1.7.6・SQLite(better-sqlite3)で、メール/パスワードの会員登録・ログイン・ログアウト・保護ページ・保護された Server Action までの完全なコードを掲載します。すべて next build → next start で動かし、Playwright(Chromium)で「登録 → 保護ページ → 名前変更 → ログアウト → 再ログイン」を実際に通した結果と、curl で確かめたエラー応答や Cookie の属性も載せています。Auth.js が Better Auth に移管された経緯と、Clerk・Supabase Auth を含めた選び方の比較表もまとめました。

Next.js 認証実装の早見表

やりたいこと この記事での実装
認証ライブラリ Better Auth 1.7.6(新規なら Auth.js 側も Better Auth を推奨)
DB SQLite(better-sqlite3)。本番は PostgreSQL・MySQL なども可
テーブル作成 npx auth@latest migrate(user・session・account・verification)
認証 API の入口 app/api/auth/[...all]/route.ts に toNextJsHandler(auth)
会員登録・ログイン Server Action から auth.api.signUpEmail / signInEmail
Server Action で Cookie を保存 plugins: [nextCookies()](最後に置く)
ログアウト auth.api.signOut → redirect('/login')
セッション取得(サーバー) auth.api.getSession({ headers: await headers() })
同じ描画内の重複取得を防ぐ React の cache() で包む
未ログインを入口で振り分け proxy.ts で getSessionCookie()(存在チェックのみ)
保護ページの本当の検証 ページの先頭で requireSession()
保護された Server Action Action の先頭でも requireSession()
エラーメッセージ APIError を catch して useActionState で表示

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Next.jsの認証実装|Better Authでログインを作る完全コード」

認証ライブラリの比較(Better Auth・Auth.js・Clerk・Supabase Auth)

観点 Better Auth Auth.js(NextAuth.js) Clerk Supabase Auth
形態 ライブラリ(自分のアプリ内で動く) ライブラリ ホスティング型サービス Supabase の機能(BaaS)
ユーザー情報の保存先 自分の DB 自分の DB(アダプター)または JWT Clerk 側 Supabase の PostgreSQL
メール/パスワード 標準で有効化できる Credentials プロバイダーで自前実装寄り 標準 標準
ソーシャルログイン 設定で追加 豊富なプロバイダー 管理画面で追加 管理画面で追加
2FA・パスキー・組織 プラグインで追加 自前実装寄り 標準機能が多い 一部標準
ログイン画面 UI 自作 自作(簡易画面あり) 用意された UI 部品 自作(UI ライブラリあり)
費用 OSS(DB・サーバー費のみ) OSS 利用量に応じた料金プラン 利用量に応じた料金プラン
npm(2026年9月時点) better-auth 1.7.6 next-auth 4.24.15(latest)/ 5.0.0-beta.32(beta) @clerk/nextjs 7.9.7 @supabase/ssr 0.12.7
向いているケース データを自社 DB に置きたい、機能を段階的に足したい 既存の Auth.js プロジェクトの保守 認証 UI や運用をまとめて任せたい DB やストレージも Supabase で揃える

選び方の目安は、「ユーザーデータを自分の DB に持ちたいか」「ログイン画面や運用をどこまで任せたいか」の2軸です。 自分の DB で持ちたく、機能を少しずつ足したいなら Better Auth、画面や多要素認証の運用ごと任せたいなら Clerk、DB も含めて一式を揃えたいなら Supabase Auth が候補になります。

Auth.js が Better Auth に移管された経緯

2025年9月26日、Auth.js(旧 NextAuth.js)の GitHub で「Auth.js is now part of Better Auth」という発表がありました。発表で確認できた内容は次のとおりです。

項目 発表の内容
保守する人 Better Auth のチームが Auth.js を保守する
既存ユーザー 使い続けてよい。セキュリティと緊急の問題への対応は続く
新機能 活発な機能開発の予定はない
新規プロジェクト 多くのチームには Better Auth を推奨。移行ガイドを用意
v5 について Better Auth のメンテナーは「v5 の直近の予定はないが、両方のバージョンを保守する」とコメント

npm では、2026年9月時点でも next-auth の latest は 4 系、v5 は beta タグのままです。すでに Auth.js で動いているプロジェクトは保守しながら使い続け、新しく作るなら Better Auth を選ぶ、という整理が発表の趣旨に沿っています。 Auth.js のサイトには Better Auth への移行ガイドも公開されています。

検証環境と完成形のファイル構成

項目 バージョン
Next.js 16.3.6(Turbopack)
React 19.2.8
Better Auth 1.7.6(CLI の auth も 1.7.6)
better-sqlite3 13.0.3(SQLite 3.53.4)
Node.js 22.22.2
Playwright / Chromium 1.63.0 / 141.0.7390.37
.env
proxy.ts                         # Cookie の存在チェック(楽観的)
lib/
├── auth.ts                      # Better Auth の設定
└── session.ts                   # getSession / requireSession(本検証)
app/
├── api/auth/[...all]/route.ts   # 認証 API の入口
├── actions/
│   ├── auth.ts                  # 登録・ログイン・ログアウト
│   └── profile.ts               # 保護された Server Action
├── auth-form.tsx                # 入力フォーム(Client Component)
├── signup/page.tsx
├── login/page.tsx
├── dashboard/page.tsx           # 保護ページ
└── page.tsx

全体の流れを図にすると次のとおりです。

sequenceDiagram
    participant B as ブラウザ
    participant P as proxy.ts
    participant S as ページ / Server Action
    participant A as Better Auth
    participant D as SQLite
    B->>S: 登録フォーム送信(Server Action)
    S->>A: auth.api.signUpEmail
    A->>D: user・account・session を保存
    A-->>B: Set-Cookie: better-auth.session_token(HttpOnly)
    B->>P: GET /dashboard
    P->>P: Cookie があるか確認(DB は見ない)
    P->>S: 通過
    S->>A: auth.api.getSession
    A->>D: セッションを照合
    S-->>B: ダッシュボードを表示

1. インストールと環境変数

create-next-app@16 で作ったプロジェクトに追加します。

npm i better-auth better-sqlite3 server-only
npm i -D @types/better-sqlite3
# .env(BETTER_AUTH_SECRET は32文字以上のランダムな値)
BETTER_AUTH_SECRET=(openssl rand -base64 32 の出力)
BETTER_AUTH_URL=http://localhost:7222

BETTER_AUTH_SECRET は暗号化やハッシュに使われる値で、公式は「32文字以上・高いエントロピーで生成」するよう求めています。 .env は Git に含めず、本番は環境変数として設定します。BETTER_AUTH_URL はアプリの URL で、本番では https:// の URL にします。

2. Better Auth の設定(lib/auth.ts)

// lib/auth.ts
import { betterAuth } from 'better-auth'
import { nextCookies } from 'better-auth/next-js'
import Database from 'better-sqlite3'

export const auth = betterAuth({
  database: new Database('sqlite.db'),
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 8,
  },
  session: {
    expiresIn: 60 * 60 * 24 * 7, // 7日
    updateAge: 60 * 60 * 24,     // 1日ごとに有効期限を延長
  },
  plugins: [nextCookies()], // Server Action から Cookie をセットするため(plugins の最後に置く)
})
設定 意味
database better-sqlite3 のインスタンスをそのまま渡せる(Node.js 22.5 以降なら node:sqlite も可)
emailAndPassword.enabled メール/パスワード認証を有効化
minPasswordLength パスワードの最小文字数
session.expiresIn / updateAge セッションの有効期間と、延長する間隔(秒)
nextCookies() Server Action の中で auth.api を呼んだとき、Set-Cookie を Next.js の cookies() 経由で反映する

nextCookies() がないと、Server Action からログインしてもブラウザに Cookie が保存されません。Server Action でログインさせるなら必須のプラグインです。

3. テーブルを作る(マイグレーション)

npx auth@latest migrate --yes

実行結果です(auth@1.7.6 で実行)。

ERROR [Better Auth]: Database schema mismatch

  Missing tables
    user, session, account, verification

  help: Run `npx auth migrate` to add the missing tables and columns.
🔑 The migration will affect the following:
-> name, email, emailVerified, image, createdAt, updatedAt fields on user table.
-> expiresAt, token, createdAt, updatedAt, ipAddress, userAgent, userId fields on session table.
-> accountId, providerId, userId, accessToken, refreshToken, idToken, accessTokenExpiresAt, refreshTokenExpiresAt, scope, password, createdAt, updatedAt fields on account table.
-> identifier, value, expiresAt, createdAt, updatedAt fields on verification table.
🚀 migration was completed successfully!

最初の ERROR は「テーブルがまだない」ことを知らせる表示で、その後に作成が完了しています。CLI のパッケージ名は、現在の公式ドキュメントでは auth(npx auth@latest ...)です。 npm の auth パッケージは説明が「The CLI for Better Auth」、リポジトリも better-auth のものでした。

テーブル 役割
user ユーザー(名前・メール・メール確認済みか)
session ログイン中のセッション(トークン・有効期限・IP・User-Agent)
account 認証手段ごとの情報。メール/パスワードは providerId = 'credential' でハッシュ化したパスワードを保存
verification メール確認やパスワード再設定のトークン

DB を見ると、パスワードは平文ではなくハッシュ化された文字列で保存されていました。

4. 認証 API の入口(Route Handler)

// app/api/auth/[...all]/route.ts
import { auth } from '@/lib/auth'
import { toNextJsHandler } from 'better-auth/next-js'

export const { GET, POST } = toNextJsHandler(auth)

これで /api/auth/sign-up/email、/api/auth/sign-in/email などの API が使えるようになります。curl で直接呼んだ結果です(Origin ヘッダーは後述の CSRF 対策のため付けています)。

$ curl -si -X POST localhost:7222/api/auth/sign-up/email \
    -H 'Content-Type: application/json' -H 'Origin: http://localhost:7222' \
    -d '{"name":"テスト太郎","email":"curl@example.com","password":"password1234"}'
HTTP/1.1 200 OK
set-cookie: better-auth.session_token=JEUXWDZH...(省略); Max-Age=604800; Path=/; HttpOnly; SameSite=Lax
{"token":"JEUXWDZH...","user":{"name":"テスト太郎","email":"curl@example.com","emailVerified":false,"image":null,"createdAt":"2026-09-29T06:14:57.400Z","updatedAt":"2026-09-29T06:14:57.400Z","id":"avxfNWXZ..."}}

Cookie には HttpOnly(JavaScript から読めない)と SameSite=Lax が付き、Max-Age=604800(7日)は設定した expiresIn と一致しました。 今回は http://localhost での検証のため Secure は付いていません。

エラー時の応答も確認しました。

送った内容 応答の本文
パスワード short(5文字) {"message":"Password too short","code":"PASSWORD_TOO_SHORT"}
登録済みのメール {"message":"User already exists. Use another email.","code":"USER_ALREADY_EXISTS_USE_ANOTHER_EMAIL"}
誤ったパスワードでログイン {"message":"Invalid email or password","code":"INVALID_EMAIL_OR_PASSWORD"}
Origin: http://evil.example でログイン HTTP/1.1 403 FORBIDDEN と {"message":"Invalid origin","code":"INVALID_ORIGIN"}

ログイン失敗時は「メールかパスワードが違う」とまとめて返し、どちらが違うかを明かしません。 また、許可していない Origin からの POST は 403 で拒否されました。別ドメインのフロントエンドから呼ぶ場合は、Better Auth の trustedOrigins にそのドメインを追加します。

5. セッション取得の共通関数(lib/session.ts)

// lib/session.ts
import 'server-only'
import { cache } from 'react'
import { headers } from 'next/headers'
import { redirect } from 'next/navigation'
import { auth } from '@/lib/auth'

// 1回の描画の中では DB への問い合わせを1回にまとめる
export const getSession = cache(async () => {
  return auth.api.getSession({ headers: await headers() })
})

// ログイン必須のページ・Server Action の先頭で呼ぶ
export async function requireSession() {
  const session = await getSession()
  if (!session) redirect('/login')
  return session
}

import 'server-only' を書くと、このファイルを Client Component から読み込んだ時点でビルドエラーになり、サーバー専用の処理がブラウザ側に混ざるのを防げます。 getSession は Cookie のトークンを DB のセッションと照合するので、これが「本当の検証」になります。Next.js 公式ガイドの Data Access Layer(DAL)の考え方と同じ形です。

6. 登録・ログイン・ログアウトの Server Action

// app/actions/auth.ts
'use server'

import { redirect } from 'next/navigation'
import { headers } from 'next/headers'
import { APIError } from 'better-auth/api'
import { auth } from '@/lib/auth'

export type FormState = { error?: string; values?: { name?: string; email?: string } } | undefined

export async function signUpAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const name = String(formData.get('name') ?? '')
  const email = String(formData.get('email') ?? '')
  const password = String(formData.get('password') ?? '')
  try {
    await auth.api.signUpEmail({ body: { name, email, password }, headers: await headers() })
  } catch (e) {
    // React 19 はアクション後にフォームをリセットするので、入力値を返して再表示する
    if (e instanceof APIError) return { error: e.message, values: { name, email } }
    throw e
  }
  redirect('/dashboard') // redirect は try の外で呼ぶ
}

export async function signInAction(_prev: FormState, formData: FormData): Promise<FormState> {
  const email = String(formData.get('email') ?? '')
  const password = String(formData.get('password') ?? '')
  try {
    await auth.api.signInEmail({ body: { email, password }, headers: await headers() })
  } catch (e) {
    if (e instanceof APIError) return { error: e.message, values: { email } }
    throw e
  }
  redirect('/dashboard')
}

export async function signOutAction() {
  await auth.api.signOut({ headers: await headers() })
  redirect('/login')
}

ポイントは3つです。

  • redirect() は try の外で呼びます。 redirect() は内部で特別な例外を投げて画面遷移させる仕組みなので、catch の中に入ると遷移が止まります。
  • APIError だけを catch して画面に返し、想定外のエラーは投げ直します。
  • エラー時に入力値を返します。 理由は次の「つまずき」で説明します。

検証で見つかったつまずき:エラー後にフォームが空になる

最初は return { error: e.message } だけを返していました。Playwright で「短いパスワードで登録 → エラー表示 → パスワードだけ直して再送信」を試したところ、再送信しても画面が進まず、30秒でタイムアウトしました。 調べると、エラー表示の時点で名前とメールの欄も空になっていました。

React 19 では、<form action> に渡したアクションが終わると、フォームの入力欄が初期値にリセットされます。 名前とメールが空になり、required のブラウザ検証で送信自体が止まっていたのが原因です。入力値を state で返し、defaultValue に入れることで解決しました(パスワードはあえて戻しません)。

7. フォームとページ

// app/auth-form.tsx
'use client'

import { useActionState } from 'react'
import type { FormState } from '@/app/actions/auth'

type Props = {
  action: (prev: FormState, formData: FormData) => Promise<FormState>
  submitLabel: string
  withName?: boolean
}

export function AuthForm({ action, submitLabel, withName }: Props) {
  const [state, formAction, pending] = useActionState(action, undefined)
  return (
    <form action={formAction}>
      {withName && (
        <label>
          名前 <input name="name" defaultValue={state?.values?.name} required />
        </label>
      )}
      <label>
        メール <input name="email" type="email" autoComplete="email" defaultValue={state?.values?.email} required />
      </label>
      <label>
        パスワード <input name="password" type="password" autoComplete="current-password" minLength={8} required />
      </label>
      <button type="submit" disabled={pending}>{submitLabel}</button>
      {state?.error && <p role="alert" data-testid="form-error">{state.error}</p>}
    </form>
  )
}
// app/login/page.tsx(signup も同じ形で、action={signUpAction}・withName を指定)
import Link from 'next/link'
import { AuthForm } from '@/app/auth-form'
import { signInAction } from '@/app/actions/auth'

export default function Page() {
  return (
    <main>
      <h1>ログイン</h1>
      <AuthForm action={signInAction} submitLabel="ログイン" />
      <Link href="/signup">会員登録はこちら</Link>
    </main>
  )
}

useActionState の3つ目の戻り値 pending で、送信中はボタンを押せないようにしています。エラーは Better Auth の英語メッセージをそのまま表示していますが、e.body?.code(例:PASSWORD_TOO_SHORT)で日本語の文言に置き換えることもできます。

8. 保護ページ(Server Component)と保護された Server Action

// app/dashboard/page.tsx
import { requireSession } from '@/lib/session'
import { signOutAction } from '@/app/actions/auth'
import { updateNameAction } from '@/app/actions/profile'

export default async function Page() {
  const { user, session } = await requireSession() // ページ側で本当の検証をする

  return (
    <main>
      <h1>ダッシュボード</h1>
      <p data-testid="welcome">{user.name} さん({user.email})でログイン中</p>
      <p>セッション有効期限: {session.expiresAt.toISOString()}</p>
      <form action={updateNameAction}>
        <input name="name" defaultValue={user.name} />
        <button type="submit">名前を変更</button>
      </form>
      <form action={signOutAction}>
        <button type="submit">ログアウト</button>
      </form>
    </main>
  )
}
// app/actions/profile.ts
'use server'

import { revalidatePath } from 'next/cache'
import { headers } from 'next/headers'
import { auth } from '@/lib/auth'
import { requireSession } from '@/lib/session'

// 保護された Server Action:proxy.ts に頼らず、ここでも必ずセッションを確認する
export async function updateNameAction(formData: FormData) {
  await requireSession()
  const name = String(formData.get('name') ?? '').trim()
  if (!name) return
  await auth.api.updateUser({ body: { name }, headers: await headers() })
  revalidatePath('/dashboard')
}

Server Action は、画面にボタンがなくても外部から POST で呼び出せる「公開 API」と同じ扱いです。 Next.js 公式も、Server Action の中で必ず認証・認可を確認するよう案内しています。ページに requireSession() があっても、Action 側にも書きます。

公式ガイドは、レイアウト(layout.tsx)での認証チェックにも注意を促しています。 レイアウトはページ遷移のたびに再描画されないため、セッションの確認は各ページやデータ取得関数(DAL)で行います。

9. proxy.ts で未ログインを入口で振り分ける

// proxy.ts
import { NextResponse } from 'next/server'
import type { NextRequest } from 'next/server'
import { getSessionCookie } from 'better-auth/cookies'

// Cookie の「存在」だけを見る楽観的チェック。本当の検証はページ・Server Action 側で行う
export function proxy(request: NextRequest) {
  const sessionCookie = getSessionCookie(request)
  if (!sessionCookie) {
    return NextResponse.redirect(new URL('/login', request.url))
  }
  return NextResponse.next()
}

export const config = {
  matcher: ['/dashboard/:path*'],
}

Better Auth のドキュメントは、getSessionCookie は Cookie の存在を確認するだけで中身を検証しないこと、保護が必要な処理では必ずサーバー側でセッションを検証することを明記しています。 Next.js 公式の認証ガイドも、Proxy はプリフェッチを含む多くのリクエストで動くため、Cookie からの楽観的チェックにとどめて DB チェックを避けるよう案内しています。

実際に、Cookie の中身が偽物の場合にどうなるかを確かめました。

# Cookie なし → proxy.ts がリダイレクト
$ curl -si localhost:7222/dashboard
HTTP/1.1 307 Temporary Redirect
location: /login

# 偽の Cookie → proxy.ts は通過するが、ページの requireSession() がリダイレクト
$ curl -si -b 'better-auth.session_token=fake' localhost:7222/dashboard
HTTP/1.1 307 Temporary Redirect
location: /login

偽の Cookie は proxy.ts を通過しますが、ページ側の getSession() が DB と照合して弾きました。 proxy.ts だけでは守れず、ページ側の検証があって初めて安全になることが分かります。

チェックの場所 何を見るか 目的
proxy.ts Cookie があるか 未ログインの人を早くログイン画面へ。表示の手間を省く
ページ(Server Component) セッションが DB に有効な状態であるか 保護されたデータを表示する前の本確認
Server Action / Route Handler 同上+権限 データを変更する前の本確認

Proxy は Node.js ランタイムで動くので、Better Auth の公式ドキュメントには proxy.ts の中で auth.api.getSession() を呼んで完全に検証する例もあります。ただし、すべての対象リクエストで DB 問い合わせが発生するため、本記事では Next.js 公式ガイドの方針に合わせ、Proxy は Cookie の確認だけにしています。

10. Playwright で一連の流れを確認

next build → next start -p 7222 で起動し、Playwright(Chromium)で「未ログインで /dashboard → 短いパスワードで登録 → 正しく登録 → 名前を変更 → ログアウト → /dashboard → 誤ったパスワード → 正しくログイン」を自動実行しました(短いパスワードの送信時は、サーバー側の検証を確かめるため minlength 属性を外しています)。

実行結果です。

1. 未ログインで/dashboard          url=/login
2. 短いパスワードで登録                url=/signup alert="Password too short"
3. 正しく登録                     url=/dashboard "備品 花子 さん(hanako@example.com)でログイン中"
   cookie: better-auth.session_token httpOnly=true sameSite=Lax secure=false expires=2026-10-06T06:17:02.567Z
   document.cookie から見えるか: ""
4. Server Actionで名前変更        url=/dashboard "備品 花子(更新) さん(hanako@example.com)でログイン中"
5. ログアウト                     url=/login cookie残り=0
6. ログアウト後に/dashboard         url=/login
7. 誤ったパスワード                  url=/login alert="Invalid email or password"
8. 正しくログイン                   url=/dashboard "備品 花子(更新) さん(hanako@example.com)でログイン中"
確認したこと 結果
未ログインで保護ページ /login へ移動
登録直後のログイン状態 そのまま /dashboard に入れた
Cookie の属性 HttpOnly・SameSite=Lax、有効期限は7日後
JavaScript から Cookie が読めるか document.cookie は空(読めない)
保護された Server Action 名前の変更が反映された
ログアウト Cookie が削除され、DB のセッションも削除(ユーザーごとのセッション数で確認)
ログアウト後の保護ページ /login へ移動
再ログイン 更新後の名前で表示

セキュリティチェックリスト

項目 この記事の実装 確認方法
シークレットが32文字以上のランダム値 openssl rand -base64 32 .env を Git に入れていないか
本番は HTTPS BETTER_AUTH_URL を https:// に Cookie に Secure が付くか
セッション Cookie が HttpOnly Better Auth の既定 document.cookie が空
SameSite が設定されている Lax(既定) レスポンスの Set-Cookie
許可していない Origin を拒否 Better Auth の既定(403) 別 Origin から POST
パスワードはハッシュで保存 Better Auth の既定 account.password が平文でない
パスワードの最小長 minPasswordLength: 8 短い値でエラー
ログイン失敗時に詳細を明かさない Invalid email or password どちらが違うかを出さない
ページで本検証 requireSession() 偽 Cookie で /login へ
Server Action でも本検証 requireSession() Action の先頭にあるか
Proxy は楽観的チェックのみ getSessionCookie() DB 問い合わせをしていない
サーバー専用コードの混入防止 import 'server-only' Client から import でビルドエラー
返すユーザー情報を最小限に 名前とメールのみ表示 パスワード等を props に渡さない
ログイン試行の制限 Better Auth のレート制限(本番で既定有効) 公式ドキュメントで設定値を確認
メール確認・パスワード再設定 本記事の範囲外 requireEmailVerification などを検討

レート制限(短時間の大量ログイン試行を制限する仕組み)は、Better Auth では本番環境で既定で有効になり、開発環境では無効です。 本記事は next start(本番モード)で動かしましたが、ログイン試行の制限値までは検証していないため、導入時は公式ドキュメントで設定を確認してください。

よくある質問

Q. proxy.ts だけでログイン必須にできますか? A. 入口の振り分けはできますが、それだけでは不十分です。今回の検証でも、偽の Cookie は proxy.ts を通過しました。Next.js 公式・Better Auth 公式ともに、ページや Server Action の中でセッションを検証するよう案内しています。

Q. Auth.js(NextAuth.js)はもう使わないほうがよいですか? A. 2025年9月の発表では、既存ユーザーは使い続けてよく、セキュリティと緊急の問題への対応は続くとされています。新しく作るプロジェクトには Better Auth が推奨されており、移行ガイドも公開されています。

Q. SQLite 以外の DB でも同じコードで動きますか? A. database に渡すものを変えれば、ほかのコードはそのまま使えます。PostgreSQL なら pg の Pool、ORM を使うなら Drizzle や Prisma のアダプターを渡します。テーブルは同じく npx auth@latest migrate(ORM の場合は generate)で用意します。

Q. クライアント側(Client Component)からログインさせたい場合は? A. better-auth/react の createAuthClient() で作ったクライアントの signIn.email() などを使います。その場合は Route Handler 経由で Cookie が保存されるため nextCookies() は不要ですが、保護ページと Server Action でのセッション検証は同じように必要です。

まとめ

  • 認証はライブラリに任せる。新規なら Better Auth、既存の Auth.js は保守しながら使い続けられる
  • Better Auth は betterAuth() の設定・toNextJsHandler・npx auth@latest migrate の3点で動き出す
  • Server Action でログインさせるなら nextCookies() プラグインが必須、redirect() は try の外で呼ぶ
  • React 19 はアクション後にフォームをリセットするので、エラー時は入力値を返して defaultValue で戻す
  • proxy.ts は Cookie の存在チェックだけ。偽の Cookie は通過するので、ページと Server Action で getSession() による本検証を行う
  • Cookie の HttpOnly・SameSite、Origin の検証、パスワードのハッシュ化は Better Auth の既定で有効なことを実際に確認できた

本記事の内容は、ご自由にお使いください。
ご利用の際は、出典として本ページへのリンクを記載いただけますようお願いします。

(記載例)出典:株式会社RJC「Next.jsの認証実装|Better Authでログインを作る完全コード」

株式会社RJCでは、安全なログイン機能のつくり方を、実際に動かして確かめながらチームで共有しています。こうした知識を共有しながら、日々技術を磨いています。

株式会社RJC ― SI事業・SES事業・AI駆動開発。RJCは一緒に成長を楽しめる会社です。

WE ARE HIRING

RJCで一緒に開発しながら、
成長を楽しみませんか?

RJCは、Web・モバイル・AIを活用した開発プロジェクトで、テックリードやPM・PMOも活躍するシステム開発会社です。会社を知る、待遇を確かめる、話を聞いてみる。気になるところ見てみてください!

  • 127日年間休日
  • 12時間平均残業時間
  • 毎日ガチャ遊びココロも大切にする福利厚生。アマギフなどの賞品ラインナップ!

ほかにも、チケットレストラン、書籍読み放題、2年ごとの慰労報奨(休暇 or 金一封)、11期連続の黒字決算。

ABOUT RJC RJCがどんな会社か知る 考え方、研修、働き方、福利厚生、社員の前職まで。RJCのことが丸わかり! RJC丸わかりページへ JOB DESCRIPTION 仕事内容・待遇を見てみる 仕事内容、給与・待遇、選考の流れ。経験者も未経験も!応募前に知りたいこと、まとめました! 募集要項を見る ENTRY エントリーする エントリーは1〜2分・履歴書不要です。まずは話を聞いてみたい、という方でも歓迎です! エントリーフォームへ

RJCで一緒に開発しながら、 成長を楽しみませんか?