Nextjs/Tanstack:Better-Auth
2026年4月6日https://www.better-auth.com/docs/introduction
PrismaAdapter
https://www.prisma.io/docs/guides/betterauth-nextjs
pnpm add better-authnpm install prisma tsx @types/pg --save-dev
npm install @prisma/client @prisma/adapter-pg dotenv pgauth.ts:
export const auth = betterAuth({
plugins: [nextCookies()],
session: {
cookieCache: {
enabled: true,
maxAge: 60 * 60 * 24 * 7,
},
},
socialProviders: {
google: {
clientId: process.env.AUTH_GOOGLE_ID as string,
clientSecret: process.env.AUTH_GOOGLE_SECRET as string,
},
},
database: prismaAdapter(prisma, {
provider: "postgresql",
}),
trustedOrigins: [env.BASE_URL as string],
databaseHooks: {}
});sessionの部分を明示的に書いているが、デフォルト値は上記と同じ
databaseHooks:の中にはUserがcreateやupdateされた時のロジックを書ける
schema.prisma:https://www.prisma.io/docs/guides/betterauth-nextjs
Schemaファイルが空なら
pnpm dlx @better-auth/cli@latest generateで生成空ではない場合はDocsからAuth関連のモデルをペースト
セッションクッキー
export const AUTH_SESSION_COOKIE = {
prod: "__Secure-better-auth.session_token",
dev: "better-auth.session_token",
};上記はデフォルトのセッションcookie名
productionだと
__Secureがcookie名の接頭辞につくので、開発時と本番ではcookie名が異なるので三項演算などが必要になる。これはcookie名を変えても同様。__Secureはhttpsじゃないとcookieを保存しないというブラウザ側の仕組み
// auth.ts
advanced: {
cookies: {
session_token: {
name: "my_app_session_token",
attributes: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
},
},
},
},名前を変える場合
SignIn / Out / Session
サーバー
サーバー側の場合 haeders()を使うので、 "use server"を書いたファイルから関数をexportする
SignIn:
const Page = () => {
return (
<form action={signInServer}>
<button type="submit">Sign in with Google</button>
</form>
);
};"use server";
import { redirect } from "next/navigation";
import { auth } from "../lib/auth";
export async function signInServer() {
const res = await auth.api.signInSocial({
body: {
provider: "google",
},
});
if (res.url) {
redirect(res.url);
}
}
*
auth.tsにplugins: [nextCookies()]を追加しないとGoogleのURLに遷移せずにエラーになる。
SignOut:
"use server";
import { headers } from "next/headers";
import { auth } from "../auth/auth";
export async function signOutServer() {
await auth.api.signOut({
headers: await headers(),
});
}Session:
const data = await auth.api.getSession({
headers: await headers(),
});
---
export const getServerSession = async () => {
return await auth.api.getSession({
headers: await headers(),
});
};クライアント
lib/auth-client.ts :
import { createAuthClient } from "better-auth/react";
export const { signIn, signUp, signOut, useSession } = createAuthClient();SignIn / Out:
const GoogleSignIn = () => {
const pathname = usePathname();
return (
<button
type="submit"
className="cursor-pointer"
onClick={() =>
signIn.social({
provider: "google",
callbackURL: pathname,
})
}
>
<FcGoogle size={20} />
</button>
);
};
export default GoogleSignIn;
export const GoogleSignOut = () => {
return (
<Button
onClick={() => {
signOut();
// window.location.reload();
router.replace("/test");
}}
type="submit"
variant={"link"}
className="cursor-pointer bg-red-500 text-white"
>
Sign out
</Button>
);
};signInはcallbackURLが無いと"/"にリダイレクトされるので、ログインしたルートにredirectさせるにはusePathname等で現在のパスを取得する。
Session:
const { data, error } = useSession();ユーザー登録
PrismaAdapterがSignIn時に自動で作る User を使うか、SignIn時に作られる User を元に独自の App_User を作成
*基本的にはPrismaAdapterで作成されたUserを拡充するだけで充分。
UserやSessionには型が用意されている。
import { Session, User } from "better-auth";独自ユーザーの作成例
callbackURL
最初にやっていた方法だが、callbackURLは認証が終わった後にユーザーをどこに戻すかが目的なので、User登録に用いるのは間違った使い方と思われる。
SignInボタン:
const GoogleSignIn = () => {
return (
<button
type="submit"
onClick={() =>
signIn.social({
provider: "google",
callbackURL: "/api/auth/user",
})
}
>
<FcGoogle size={20} />
</button>
);
};callbackURLはGoogleサインインが正常に行われた後にブラウザが遷移する先
route.ts:
export async function GET() {
const session = await getServerSession();
if (!session) {
return NextResponse.redirect(`${BASE_URL}/sign-in`);
}
const { email, name, image } = session.user;
// ユーザー登録処理
// リダイレクト
return NextResponse.redirect(`${BASE_URL}/`);
}
}Sessionの所に書いた使い回し可能なSession取得関数を用いてSession情報をもとに独自のUserテーブルを作成
*
returnするのはNextResponse.jsonではなくNextResponse.redirectにする。NextResponse.jsonだとAPIルートがブラウザに表示されたままになる
databaseHooks
auth.ts:
export const auth = betterAuth({
...
databaseHooks: {
user: {
create: {
after: async (user) => {
if (!user?.email) return;
const existingUser = await prisma.app_user.findUnique({
where: { email: user.email },
});
if (existingUser) return;
await prisma.app_user.create({
data: {
email: user.email,
name: user.name,
image: user.image,
},
});
},
},
},
},
});databaseHooks内にSignInしたユーザーが登録している/していない場合の処理を書くSignInボタンの
callbackURLはリダイレクトさせたいルートにする
複数OAuthと同一Email
例えばGoogleでSignInをしてUserが作成された後に、Googleと同じEmailを使っているGihubでSignInしたときには新規Userではなく、既存のUserに紐付けたい。
auth.ts:
import { prisma } from "@/prisma/prisma";
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
export const auth = betterAuth({
secret: process.env.BETTER_AUTH_SECRET,
baseURL: process.env.BETTER_AUTH_URL,
database: prismaAdapter(prisma, { provider: "postgresql" }),
socialProviders: {
google: {
clientId: process.env.AUTH_GOOGLE_ID as string,
clientSecret: process.env.AUTH_GOOGLE_SECRET as string,
},
github: {
clientId: process.env.AUTH_GITHUB_ID as string,
clientSecret: process.env.AUTH_GITHUB_SECRET as string,
},
},
account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "github"],
},
},
export default auth;accountLinkingの部分を追加する。動作としては、accountテーブルはOAuth毎に重複なしで作成されていくが、UserテーブルはEmailが同じなら既存のUserに紐付けられる。
Tanstack
auth.ts:
import { betterAuth } from "better-auth";
import { tanstackStartCookies } from "better-auth/tanstack-start";
import { env } from "cloudflare:workers";
export const auth = betterAuth({
database: env.test_db,
socialProviders: {
google: {
clientId: process.env.AUTH_GOOGLE_ID,
clientSecret: process.env.AUTH_GOOGLE_SECRET,
},
},
plugins: [tanstackStartCookies()],
baseURL: process.env.BETTER_AUTH_URL,
});PrismaAdapterを使うことができないが、
databaseにD1のDBを書くとSignIn時にUserがテーブルに作成されるので使用感はNextjs x Prisma統合と変わらない。