0.はじめに
以前、NextAuth.js (Auth.js v5) を用いた堅牢な認証システムの記事を書きましたが、今回は評判の Better Auth に移行してみました。
以前の記事
✅ 世間の評判:NextAuth vs Better Auth (2026年現在)
現在、React/Next.js界隈では、認証ライブラリの選定において大きなパラダイムシフトが起きています。
| 特徴 | NextAuth.js (Auth.js) | Better Auth |
|---|---|---|
| 立ち位置 |
業界のスタンダード 実務での採用率No.1。エコシステムが巨大で、困ったときの情報量が圧倒的。 |
新進気鋭のチャレンジャー TypeScriptの型推論が最強。2要素認証やパスキーなどが驚くほど簡単に実装できる。 |
| 強み | 歴史が長く、多くのプロバイダーに対応。V5でApp Router対応が強化された。 |
型安全性 (Type Safety)。 プラグインシステムにより、機能追加(Admin機能など)が容易。 |
| 弱点 | カスタマイズ(特に型拡張)がやや複雑で、ドキュメントが難解になりがち。 | まだ歴史が浅く、枯れていない部分がある。 |
本記事では、 Better Auth の目玉機能である「2要素認証」や「メールアドレス確認」などにはあえて踏み込みません。
一般的な業務アプリで求められるシンプルな認証・認可のみにフォーカスし、「それでもなお NextAuth から移行するメリット(開発体験の向上)は得られるのか?」という点を中心にまとめています。
1.デモアプリのイメージとソースコード
アカウト作成も出来ますが、以下で管理者としてログインできます。
admin@test.com
kyouhayuki
✅ ソースコード
2.この記事での防衛方針と環境
✅この記事での防衛方針(Better Auth版)
ライブラリが変わっても、守るべきセキュリティラインは変わりません。
-
防衛ライン① :
proxy.tsでレンダリング前に弾く - 防衛ライン② : Better Authによるセッション管理とDB保存
- 防衛ライン③ : 無操作時の自動ログアウト設定
- 防衛ライン④ : プラグインを用いた認可(Role管理)
- 防衛ライン⑤ : Route Groupsによる隔離 (protected)
Next.js 16から middleware.ts が非推奨になり ファイル名が proxy.ts に変わりました
✅ 技術スタックの変更点
NextAuth版から以下のように構成を変更・強化しました。
| 過去のミドルウエアなど | 今回採用したミドルウエアなど | バージョン |
|---|---|---|
| NextAuth.js | Better Auth.js | 1.4.15 |
| Prisma | Drizzle ORM | 0.45.1 |
| Next.js | → | 16.1.3 |
| React | → | 19.2.3 |
| tailwindcss | → | 4 |
3.最終ディレクトリ構成
Better Authはクライアント側とサーバー側の責務が明確です。リポジトリの構成をベースに整理しました。
src/
├── app/
│ ├── (protected)/ # Route Group(認証必須)
│ │ ├── admin/ # 管理者専用
│ │ ├── user/ # 一般専用
│ │ └── page.tsx # ルートページ
│ ├── login/ # ログインページ(認証不要)
│ ├── signup/ # サインアップページ(認証不要)
│ └── api/
│ └── auth/
│ └── [...all]/
│ └── route.ts # Better Auth APIエンドポイント
├── db/
│ └── schema.ts # Drizzleスキーマ (User, Session等)
├── lib/
│ ├── auth.ts # Server SDK (検証・ガード用)
│ ├── auth-client.ts # Client SDK (操作・トリガー用)
│ └── auth-guard.ts # 認証・認可ガード関数群│
.env # 環境変数の定義
proxy.ts # 認証・認可制御 最大の門番
単純にかなりファイル数が減ったことが分かります
4.防衛ライン①:proxy.ts で安全な認証・認可制御
NextAuth同様、ページレンダリング「前」にリクエストを遮断します。Better Authではセッション取得の方法が少し変わりますが、役割は同じです。
proxy.ts
import { NextRequest, NextResponse } from "next/server";
import { headers } from "next/headers";
import { auth } from "@/lib/auth";
export async function proxy(request: NextRequest) {
const { pathname } = request.nextUrl;
// 1. 公開パスの定義
const publicPaths = ["/login", "/signup"];
const isPublicPath = publicPaths.some(path => pathname.startsWith(path));
const isAdminPath = pathname.startsWith("/admin");
// 2. セッションの取得(Better Authによる厳密な検証)
const session = await auth.api.getSession({
headers: await headers(),
});
// -------------------------------------------------------------
// パターンの分岐
// -------------------------------------------------------------
// ケース1:未ログイン + 公開パス以外にアクセスしようとした
if (!session && !isPublicPath) {
return NextResponse.redirect(new URL("/login", request.url));
}
// ケース2:ログイン済み + ログイン・登録系ページにアクセスしようとした(逆流防止)
if (session && isPublicPath) {
// 認証済みルート((protected)/page.tsx すなわち "/")へリダイレクト
return NextResponse.redirect(new URL("/", request.url));
}
// ケース3:【認可判定】Admin専用パス + 管理者以外がアクセス
// ロールが 'admin' と完全一致しない場合はすべて拒否(フェイルセーフ)
if (isAdminPath && session?.user.role !== "admin") {
// 403 Forbidden の代わりにトップページへリダイレクト
return NextResponse.redirect(new URL("/", request.url));
}
return NextResponse.next();
}
export const config = {
/*
* 以下のパス以外すべてに proxy を適用する:
* 1. api (API routes)
* 2. _next/static (static files)
* 3. _next/image (image optimization files)
* 4. favicon.ico, sitemap.xml, robots.txt (metadata files)
*/
matcher: [
'/((?!api|_next/static|_next/image|favicon.ico|sitemap.xml|robots.txt).*)',
],
};
5.防衛ライン②・③・④:Better Authの設定 (auth.ts)
NextAuthでは auth.ts や types.d.ts で複雑な型パズルをする必要がありましたが、Better Authは 「プラグイン」 と 「スキーマ駆動」 でこれを解決します。
✅ lib/auth.ts の記述
ここですべてのセキュリティ設定(防衛ライン②~④)を集約します。
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { db } from "@/db"; // Drizzleのインスタンス
import { admin } from "better-auth/plugins/admin";
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // PostgreSQLなどを想定
}),
// 防衛ライン②: メール/パスワード認証 (bcrypt等は内部で自動処理)
emailAndPassword: {
enabled: true,
},
// 防衛ライン③: セッション管理 (自動ログアウト)
session: {
expiresIn: 60 * 30, // 30分 (seconds)
updateAge: 60 * 5, // 5分ごとに有効期限を更新
},
// 防衛ライン④: 認可 (Role管理)
// NextAuthのように手動で型拡張せずとも、プラグインを入れるだけで完了
plugins: [
admin(),
],
});
6.防衛ライン⑤ : Route Groupsによる隔離 (protected)
プロジェクト構造を見ると、認証が必要なページはすべて src/app/(protected)/ という特殊なフォルダの下に配置されています。
src/
├── app/
│ ├── (protected)/ # Route Group(認証必須)
│ │ ├── admin/ # 管理者専用
│ │ ├── user/ # 一般専用
│ │ └── page.tsx # ルートページ
( ) で囲まれたフォルダ名はURLには影響しませんが、開発者が「ここは保護されたエリアだ」と認識し、レイアウト(layout.tsx)や設定を共有するのに役立ちます。
7.クライアント側の実装 (型安全なクライアント)
Better Authの最大の魅力はここです。Reactから利用される一般的な認証・認可関連の操作はSDKとして提供されています。
src/lib/auth-client.ts
import { createAuthClient } from "better-auth/react";
// -----------------------------------------------------------
// Client SDK
// 一般的なユーザー操作(トリガー)はこのSDKがカバーする
// -----------------------------------------------------------
// ログイン (signIn)
// 新規登録 (signUp)
// ログアウト (signOut)
// -- 非推奨 -------------------------------------------------
// クライアント側でのセッション取得 (useSession) ※ React Hook (監視用: UI連動)
// クライアント側でのセッション取得 (getSession) ※ Async Func (点呼用: イベント時)
// ※ データ取得は原則 Server Components (Page/Layout) で行い、Propsで渡すこと
// -----------------------------------------------------------
export const authClient = createAuthClient({
baseURL: process.env.NEXT_PUBLIC_BETTER_AUTH_URL, // 例: http://localhost:3000
});
export const { signIn, signUp, signOut } = authClient;
✅ Better Auth クライアントSDKを利用した ログイン画面の実装
NextAuthの記事では useActionState とサーバーアクションを組み合わせて実装しましたが、Better Authでは提供されている クライアントSDKを利用できるので最小限のコードで実現できます。
"use client";
import { signIn } from "@/lib/auth-client";
import { toast } from "sonner";
import { useRouter } from "next/navigation";
export default function LoginPage() {
const router = useRouter();
// フォーム送信ハンドラ (react-hook-form等)
async function onSubmit(values: z.infer<typeof formSchema>) {
await signIn.email(
{
email: values.email,
password: values.password,
},
{
onSuccess: () => {
router.refresh(); // セッション更新
router.push("/");
},
onError: (ctx) => {
toast.error(ctx.error.message);
},
}
);
}
return ( /* JSX */ );
}
8.Navigation実装の注意点
Next.js App Router のパフォーマンスを最大化する設計パターンです。
- Server Parent:
layout.tsx でセッションを取得し、必要な情報(isAdminなど)のみを算出。 - Client Child:
Propsとして受け取り、描画に専念する。
✅ 実装イメージ
Server Side (Layout)
// app/(protected)/layout.tsx
export default async function Layout({ children }) {
const session = await auth.api.getSession({ headers: await headers() });
const isAdmin = session?.user.role === "admin";
return (
<>
<Navbar isAdmin={isAdmin} user={session?.user} />
{children}
</>
);
}
Client Side (Navbar)
"use client";
// useSessionフックは使わない! Propsで受け取るだけ。
export function Navbar({ isAdmin, user }) {
return (
<nav>
{isAdmin && <AdminLink />}
<div>{user.name}</div>
</nav>
);
}
✅ 得られる効果
- レイアウトシフト(ちらつき)ゼロ:
useSession 特有のローディング状態がなくなります。 - 通信削減:
クライアントからAPIへの余計なフェッチが発生しません。 - セキュリティ:
フロントエンドに渡す情報をサーバー側でコントロールできます。
9.まとめ
デモアプリをNextAuthからBetter Authへ移行し、Drizzle ORMと組み合わせて再構築しました。
Better Auth は本来、パスキーや2要素認証(2FA)といった高度な機能を簡単に導入できることが売りですが、一般的な業務アプリケーションで利用される範囲に限っても、開発体験(DX)の向上を体感できました。
-
型定義の手間が激減
あの難解な next-auth.d.ts を定義する「型パズル」から完全に解放されました。Drizzle のスキーマ定義と連動し、自然に型が推論されます。 -
コード量の削減と可読性向上
充実したクライアント用・サーバー用 SDK のおかげで、ボイラープレート記述が減り、認証ロジックがシンプルになりました。 -
責務の明確な分離
Server SDK(検証・DB操作)と Client SDK(フック・UI操作)が明確に分かれているため、Next.js の Server Actions / Client Components の設計思想と非常にマッチします。
2026年現在、新規開発でTypeScriptを全面的に採用するなら、Better Auth は間違いなく最有力候補だと思います。
Disclaimer: 本記事の内容は執筆時点(2026年)の情報に基づきます。本番環境への導入は十分な検証を行ってください。

