はじめに
Next.jsで認証機能を実装する際によく聞くライブラリといえばNextAuth.jsやAuth.jsといったものが有名です。今回はBetterAuthというフレームワークに非依存かつプラグインによって簡単にパスキーや二段階認証といったセキュリティ機能を作成できるライブラリを使い、またデータベース接続部分にPrismaを使用して認証機能を実装していきたいと思います。認証プロバイダはDiscordを使用します。Googleなど別のプロバイダを利用する場合は、適宜クライアントと秘密鍵を取得し設定してください。
Better-Authとは?
Better-AuthはTypeScript向けに最適化された、フレームワークに依存せず、簡単に認証機能を実装できる認証ライブラリです。2025/03/10時点での最新バージョンはv1.2.3
と割と最近のライブラリです。
認証プロバイダとして、メールアドレスとパスワードのほかにAppleやGithub、Google、Microsoftなど数多くのプロバイダを利用できます。また、データベースはMySQL、PostgreSQL、SQLiteに対応しています。
最大の目玉にプラグインがあり、こちらは二段階認証やパスキー、Google One Tap、SSOなどを簡単に追加できるほか、JWT認証やCAPTCHA認証なども追加できます。
Prismaとは?
Prismaは、Node.jsでオープンソースで利用できるORM(Object-Relational Mapping)です。
通常データベースとやり取りする場合はSQL文でやり取りする必要があり、大変面倒です。そこで、Prismaを使うことでJavaScript・TypeScriptで記述でき、型安全なデータベースアクセスができるようになります。ちなみにPrismaは多くのデータソースとフレームワークに対応しているためデータベースアクセスするのがかなり楽になります。
Next.jsのセットアップ
まずはNext.jsのセットアップを行います。Next.jsは最新版を使っていきます。
今回はBunを使っているのでbunx
を使用していますが、npmを使う場合はnpx
に読み替えてください。
bunx create-next-app
√ What is your project named? ... next-auth
√ Would you like to use TypeScript? ... No / > Yes
√ Would you like to use ESLint? ... No / > Yes
√ Would you like to use Tailwind CSS? ... No / > Yes
√ Would you like your code inside a `src/` directory? ... No / > Yes
√ Would you like to use App Router? (recommended) ... No / > Yes
√ Would you like to use Turbopack for `next dev`? ... > No / Yes
√ Would you like to customize the import alias (`@/*` by default)? ... > No / Yes
Creating a new Next.js app in xxxxxx.
TypeScript、ESLint、Tailwind CSS、srcディレクトリの使用、App Routerの使用はそれぞれYesです。Turbopackとインポートエイリアスの変更は今回行わないのでNoにしています。ここは適宜変更してください。
Prisma、Better-Authのインストール
PrismaとBetter-Authを依存関係に追加していきます。
bun add prisma better-auth @prisma/client
Shadcn/uiのインストール
Better-Authのログイン画面にはshadcn/uiが使用されています。手っ取り早く使うことができるので追加しておきます。
bunx --bun shadcn@latest init
bunx
ではbunのバージョンが古い場合にパッケージ名が重複してpostinstallなどに失敗する場合があります。その場合はbun upgrade
でbunを最新バージョンにしたのち、bun pm cache rm
でキャッシュを削除して再度試してみてください。
このようになればOKです。
✔ Preflight checks.
✔ Verifying framework. Found Next.js.
✔ Validating Tailwind CSS config. Found v4.
✔ Validating import alias.
√ Which color would you like to use as the base color? » Neutral
✔ Writing components.json.
✔ Checking registry.
✔ Updating src\app\globals.css
✔ Installing dependencies.
✔ Created 1 file:
- src\lib\utils.ts
Success! Project initialization completed.
また、ログインフォームのためにコンポーネントを追加します。
bunx shadcn add
コマンドで、コンポーネントを選択してインストールすることができます。インストールが必要なコンポーネントは次の通りです。
- Button
- Card
- Input
- Label
- Checkbox
Better-Authのセットアップ
src/lib
にauth.ts
を作成します。
import { betterAuth } from "better-auth";
import { PrismaClient } from "@prisma/client";
import { prismaAdapter } from "better-auth/adapters/prisma";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "mysql",
}),
socialProviders: {
discord: {
clientId: process.env.DISCORD_CLIENT_ID as string,
clientSecret: process.env.DISCORD_CLIENT_SECRET as string,
},
// ほかのプロバイダを使う場合はここに追加
},
});
今回はDiscordのみの認証で、データベースにMySQLをPrisma経由で通信します。
DiscordのクライアントIDと秘密鍵は、Discordのデベロッパーポータルから新しいアプリケーションを作成してそれぞれ設定してください。
なお、Prismaを介してデータベースに接続する場合はBetter-Authが提供している prismaAdapter
を使います。
次に、Next.jsのAPI Routeを作成します。src/auth/[...all]/route.ts
を作成します。
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
最後に、lib/auth-client.ts
を作成し、クライアント側を実装します。
import { createAuthClient } from "better-auth/react";
import { toast } from "sonner";
export const client = createAuthClient({
baseURL: process.env.BETTER_AUTH_URL,
fetchOptions: {
onError(e) {
if (e.error.status === 429) {
toast.error("Too many requests. Please try again later.");
}
},
},
});
export const { signUp, signIn, signOut, useSession } = client;
client.$store.listen("$sessionSignal", async () => {});
環境変数の設定
ルートディレクトリに.env
ファイルを作成します。.env.local
では後述するPrismaのスキーマファイルから読み取ることができないため.env
を使用してください。
MYSQL_DATABASE_URL="mysql://<user>:<password>@<host address>/<db name>?schema=public"
BETTER_AUTH_SECRET=""
BETTER_AUTH_URL="http://localhost:3000"
DISCORD_CLIENT_ID=""
DISCORD_CLIENT_SECRET=""
Better-Authの秘密鍵は、次のサイトにある「Set Environment Variables」内のSecret Keyの項目にある「Generate Secret」ボタンを押すことで生成されます。OpenSSLで生成することもできます。
ログインフォームの作成
にある「Create Sign in Box」ボタンを押し、使用するプロバイダなどを設定し、フレームワークを選ぶだけでshadcn/uiによるコンポーネントが生成されます。
生成されたコンポーネントをsrc/components/sign-in.tsx
に貼り付け、signinページで利用します。以下は生成された例です。
"use client";
import { Button } from "@/components/ui/button";
import {
Card,
CardContent,
CardHeader,
CardTitle,
CardDescription,
CardFooter,
} from "@/components/ui/card";
import { useState } from "react";
import { Loader2, Key } from "lucide-react";
import { signIn } from "@/lib/auth-client";
import Link from "next/link";
import { cn } from "@/lib/utils";
export default function SignIn() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [loading, setLoading] = useState(false);
return (
<Card className='max-w-md'>
<CardHeader>
<CardTitle className='text-lg md:text-xl'>Sign In</CardTitle>
<CardDescription className='text-xs md:text-sm'>
Enter your email below to login to your account
</CardDescription>
</CardHeader>
<CardContent>
<div className='grid gap-4'>
<div
className={cn(
"w-full gap-2 flex items-center",
"justify-between flex-col"
)}>
<Button
variant='outline'
className={cn("w-full gap-2")}
onClick={async () => {
await signIn.social({
provider: "discord",
// 遷移先を変更する場合はここを変更
callbackURL: "/dashboard",
});
}}>
<svg
xmlns='http://www.w3.org/2000/svg'
width='1em'
height='1em'
viewBox='0 0 24 24'>
<path
fill='currentColor'
d='M19.27 5.33C17.94 4.71 16.5 4.26 15 4a.1.1 0 0 0-.07.03c-.18.33-.39.76-.53 1.09a16.1 16.1 0 0 0-4.8 0c-.14-.34-.35-.76-.54-1.09c-.01-.02-.04-.03-.07-.03c-1.5.26-2.93.71-4.27 1.33c-.01 0-.02.01-.03.02c-2.72 4.07-3.47 8.03-3.1 11.95c0 .02.01.04.03.05c1.8 1.32 3.53 2.12 5.24 2.65c.03.01.06 0 .07-.02c.4-.55.76-1.13 1.07-1.74c.02-.04 0-.08-.04-.09c-.57-.22-1.11-.48-1.64-.78c-.04-.02-.04-.08-.01-.11c.11-.08.22-.17.33-.25c.02-.02.05-.02.07-.01c3.44 1.57 7.15 1.57 10.55 0c.02-.01.05-.01.07.01c.11.09.22.17.33.26c.04.03.04.09-.01.11c-.52.31-1.07.56-1.64.78c-.04.01-.05.06-.04.09c.32.61.68 1.19 1.07 1.74c.03.01.06.02.09.01c1.72-.53 3.45-1.33 5.25-2.65c.02-.01.03-.03.03-.05c.44-4.53-.73-8.46-3.1-11.95c-.01-.01-.02-.02-.04-.02M8.52 14.91c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.84 2.12-1.89 2.12m6.97 0c-1.03 0-1.89-.95-1.89-2.12s.84-2.12 1.89-2.12c1.06 0 1.9.96 1.89 2.12c0 1.17-.83 2.12-1.89 2.12'></path>
</svg>
Sign in with Discord
</Button>
</div>
</div>
</CardContent>
</Card>
);
}
サインインページ、コールバックページの作成
先ほど作成したコンポーネントをログインページで使用します。src/app/login/page.tsx
を作成し、次のコードを貼り付けます。
"use client";
//適宜置き換えてください
import SignIn from "@/components/sign-in";
export default function Login() {
return (
<div className='w-full'>
<div className='flex items-center flex-col justify-center w-full md:py-10'>
<div className='md:w-[400px]'>
<SignIn />
</div>
</div>
</div>
);
}
ログイン後、/dashboard
ページに移動するようになっています。そのためdashboardページを作成します。テストとして、ログイン後のデータをjson形式で表示するだけにしてみます。
src/app/dashboard/page.tsx
を作成し、次のコードを貼り付けます。
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
import { redirect } from "next/navigation";
export default async function Dashboard() {
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
redirect("/");
}
return (
<div>
<h1>Dashboard</h1>
<pre>{JSON.stringify(session, null, 2)}</pre>
</div>
);
}
これでBetter-Authとフロントエンド側の設定は完了しました! 次にPrisma側の設定をしていきます。
Prismaのスキーマ生成
bunx prisma init
でスキーマファイルを作成できます。スキーマファイルは.prisma/schema.prisma
になっています。生成後のファイルはPostgreSQLを使う設定になっているのでProviderをmysql
に、.envの参照をMYSQL_DATABASE_URL
に変更します。
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "mysql"
url = env("MYSQL_DATABASE_URL")
}
次に、Better-AuthのCLIを用いてテーブルなどの情報を生成します。
bunx @better-auth/cli@latest generate
でPrisma用のスキーマを自動生成してくれます。スキーマが生成された後はPrismaのクライアントを生成します。
bunx prisma generate
✔ Generated Prisma Client (v6.4.1) to .\node_modules\@prisma\client in 64ms
Start by importing your Prisma Client (See: https://pris.ly/d/importing-client)
Help us improve the Prisma ORM for everyone. Share your feedback in a short 2-min survey: https://pris.ly/orm/survey/release-5-22
となれば成功です。
Prismaのマイグレーション
では最後にPrisma経由で実際にテーブルを作成します。
bunx prisma migrate dev --name init
Applying migration `20250310065802_init`
The following migration(s) have been created and applied from new schema changes:
migrations/
└─ 20250310065802_init/
└─ migration.sql
Your database is now in sync with your schema.
✔ Generated Prisma Client (v6.4.1) to .\node_modules\@prisma\client in 66ms
となれば成功です。PhpMyAdminなどでテーブルが作成されていることが確認できればデータベースの接続が完了です。
なお、
Error: P1010
User `xxxx` was denied access on the database `xxxx`
というエラーが出た場合は、そのユーザーにCREATE, ALTER, DROP, REFERENCES ON *.*
の権限を付与すると成功1します。なお、この方法は開発環境の場合のみ必要です。
本番環境で使う場合は本番環境用のユーザーを作成し、必要最低限の権限を付与したうえでbunx prisma migrate deploy
を実行する必要があります。
実際に、localhost:3000/login にアクセスし、Discordのアカウントでログインするとダッシュボードページにセッション情報がJSON形式で表示されていると思います。
おわりに
Better-AuthとPrismaを用いることでデータベースを簡単に接続することができました。
なお、この記事は本番環境を想定しておらずあくまで開発環境で実行することを想定しています。そのため本番環境で使用する場合は、Prismaのマイグレーションをデプロイモードにするなどを確認しておくことをおすすめします。