認証
Better Authによるユーザー認証 - サインアップ、ログイン、OAuth、セッション管理
認証
ProductReadyは、モダンで安全な認証のためにBetter Authを使用しています。このガイドでは、基本的なメール/パスワードからOAuth統合まですべてをカバーします。
なぜBetter Auth? モダン、型安全、フレームワークに依存しない、活発にメンテナンスされています。NextAuth/Auth.jsよりもはるかにシンプルです。
クイック概要
サポートされる方法:
- ✅ メール + パスワード
- ✅ OAuth(GitHub、Google)
- ✅ マジックリンク(メールのみのログイン) - 設定可能
- ✅ セッション管理
- ✅ メール確認 - オプション
仕組み:
- ユーザーがサインアップ/ログイン
- Better Authがセッションを作成
- セッションがデータベース + Cookieに保存
- tRPCが保護されたルートのセッションをチェック
サインアップとログイン
メール + パスワードフロー
ユーザーはメールとパスワードでアカウントを作成できます。
サインアップページ: /sign-up(またはカスタムの場所)
コンポーネント例:
'use client';
import { authClient } from '~/lib/auth.client';
import { useState } from 'react';
export function SignUpForm() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
try {
await authClient.signUp.email({
email,
password,
name,
});
// ダッシュボードにリダイレクト
window.location.href = '/dashboard';
} catch (error) {
console.error('サインアップ失敗:', error);
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="text"
placeholder="名前"
value={name}
onChange={(e) => setName(e.target.value)}
/>
<input
type="email"
placeholder="メール"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
<input
type="password"
placeholder="パスワード"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
<button type="submit">サインアップ</button>
</form>
);
}ログインも同様です - authClient.signIn.email()を使用:
await authClient.signIn.email({
email,
password,
});OAuth認証
GitHubでログイン
設定:
-
GitHub OAuth Appを作成:
- GitHub Developer Settingsにアクセス
- 「New OAuth App」をクリック
- コールバックURL:
http://localhost:3000/api/auth/callback/github
-
環境変数を設定:
BETTER_AUTH_GITHUB_CLIENT_ID=your_github_client_id
BETTER_AUTH_GITHUB_CLIENT_SECRET=your_github_client_secret- コンポーネントで使用:
import { authClient } from '~/lib/auth.client';
export function GitHubSignIn() {
async function handleGitHubSignIn() {
await authClient.signIn.social({
provider: 'github',
callbackURL: '/dashboard',
});
}
return (
<button onClick={handleGitHubSignIn}>
GitHubでサインイン
</button>
);
}Googleでログイン
同様のプロセス:
- Google Cloud ConsoleでOAuthクライアントを作成
- 環境変数を設定:
BETTER_AUTH_GOOGLE_CLIENT_ID=your_google_client_id
BETTER_AUTH_GOOGLE_CLIENT_SECRET=your_google_client_secret- コンポーネントで使用:
await authClient.signIn.social({
provider: 'google',
callbackURL: '/dashboard',
});セッション管理
サーバーサイドでセッションを確認
tRPCルーター内:
import { protectedProcedure } from '~/server/trpc';
export const userRouter = router({
getProfile: protectedProcedure
.query(async ({ ctx }) => {
// ctx.sessionにユーザー情報が含まれます
const userId = ctx.session.user.id;
return await ctx.db.query.users.findFirst({
where: eq(users.id, userId),
});
}),
});クライアントサイドでセッションを確認
Reactコンポーネント内:
'use client';
import { authClient } from '~/lib/auth.client';
import { useEffect, useState } from 'react';
export function ProfilePage() {
const [session, setSession] = useState(null);
useEffect(() => {
authClient.getSession().then(setSession);
}, []);
if (!session) {
return <div>読み込み中...</div>;
}
return (
<div>
<h1>ようこそ、{session.user.name}さん</h1>
<p>メール: {session.user.email}</p>
</div>
);
}ログアウト
import { authClient } from '~/lib/auth.client';
export function LogoutButton() {
async function handleLogout() {
await authClient.signOut();
window.location.href = '/';
}
return (
<button onClick={handleLogout}>
ログアウト
</button>
);
}ルートの保護
ページレベルの保護
Server Component:
import { auth } from '~/lib/auth';
import { redirect } from 'next/navigation';
export default async function DashboardPage() {
const session = await auth();
if (!session) {
redirect('/sign-in');
}
return (
<div>
<h1>ダッシュボード</h1>
<p>ようこそ、{session.user.name}さん</p>
</div>
);
}API保護
tRPCではprotectedProcedureを使用:
import { protectedProcedure, router } from '~/server/trpc';
export const tasksRouter = router({
list: protectedProcedure
.query(async ({ ctx }) => {
// セッションは既に検証済み
const userId = ctx.session.user.id;
return await ctx.db.query.tasks.findMany({
where: eq(tasks.userId, userId),
});
}),
});パスワードリセット
リセットをリクエスト:
await authClient.forgetPassword({
email: 'user@example.com',
redirectTo: '/reset-password',
});パスワードをリセット:
await authClient.resetPassword({
token: 'reset_token_from_email',
password: 'new_password',
});メール確認
確認メールを送信:
await authClient.sendVerificationEmail({
email: 'user@example.com',
callbackURL: '/verify',
});メールを確認:
await authClient.verifyEmail({
token: 'verification_token_from_email',
});ベストプラクティス
セキュリティ
- ✅ 強力なパスワードを強制:最低8文字、大文字、数字
- ✅ レート制限:ログイン試行を制限
- ✅ HTTPS:本番環境では常にHTTPSを使用
- ✅ 環境変数:シークレットをコードにハードコードしない
ユーザーエクスペリエンス
- ✅ 明確なエラーメッセージ:ユーザーに何が問題かを伝える
- ✅ ローディング状態:非同期操作中にローダーを表示
- ✅ リダイレクト:ログイン後、ユーザーを適切な場所に誘導
- ✅ 「ログイン状態を保持」:セッションの有効期限を長くするオプション
トラブルシューティング
セッションが持続しない
問題:ログイン後、ページリフレッシュでログアウトされる。
解決策:
- Cookieが正しく設定されていることを確認
BETTER_AUTH_SECRETが設定されていることを確認- 開発環境では
localhostを使用(127.0.0.1ではなく)
OAuth リダイレクトが機能しない
問題:OAuth後に404エラー。
解決策:
- OAuthプロバイダーのコールバックURLが正しいことを確認
.envファイルに正しいクライアントIDとシークレットがあることを確認- サーバーを再起動
次のステップ
認証を設定したので:
参考資料
ProductReadyで認証を楽しんでください! 🔐