better-auth-security-best-practices について
アプリケーションで認証を設定する開発者向けのBetter Authセキュリティガイドです。シークレット、レート制限、CSRF、信頼済みオリジン、セッション、Cookie、OAuthトークン、監査フックを扱います。
主な機能
- 01シークレット設定の指針
- 02レート制限とストレージ設定
- 03CSRFと信頼済みオリジンの制御
- 04セッションとCookieの保護
- 05OAuthトークンの保護
- 06セキュリティ監査フック
活用シーン
- 01本番環境向けBetter Auth設定
- 02機密性の高いエンドポイントの制限設定
- 03セッションとサブドメイン間Cookieの保護
- 04認証処理への監査ログ追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ better-auth/ skills --skill better-auth-security-best-practicesインストール後、Claude Code で/better-auth-security-best-practicesを入力して読み込まれたか確認します。
詳細
シークレット管理
シークレットの設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
secret: process.env.BETTER_AUTH_SECRET, // or via `BETTER_AUTH_SECRET` env
});
Better Auth は、次の順序でシークレットを検索します。
- 設定内の
options.secret BETTER_AUTH_SECRET環境変数AUTH_SECRET環境変数
シークレットの要件
- 本番環境では、デフォルトまたはプレースホルダーのシークレットを拒否します
- 32 文字未満、またはエントロピーが 120 ビット未満の場合に警告します
- 生成方法:
openssl rand -base64 32 - シークレットをバージョン管理にコミットしないでください
レート制限
本番環境ではデフォルトで有効です。すべてのエンドポイントに適用されます。プラグインはエンドポイントごとに設定を上書きできます。
デフォルト設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
rateLimit: {
enabled: true, // Default: true in production
window: 10, // Time window in seconds (default: 10)
max: 100, // Max requests per window (default: 100)
},
});
ストレージのオプション
オプション:"memory"(再起動時にリセットされるため、サーバーレス環境では使用しないでください)、"database"(永続化)、"secondary-storage"(Redis。利用可能な場合のデフォルト)。
rateLimit: {
storage: "database",
}
カスタムストレージ
独自のレート制限ストレージを実装します。
rateLimit: {
customStorage: {
get: async (key) => {
// Return { count: number, expiresAt: number } or null
},
set: async (key, data) => {
// Store the rate limit data
},
},
}
エンドポイントごとのルール
機密性の高いエンドポイントでは、デフォルトで 10 秒あたり 3 リクエストに制限されます(/sign-in、/sign-up、/change-password、/change-email)。上書きするには、次のようにします。
rateLimit: {
customRules: {
"/api/auth/sign-in/email": {
window: 60, // 1 minute window
max: 5, // 5 attempts
},
"/api/auth/some-safe-endpoint": false, // Disable rate limiting
},
}
CSRF 保護
複数層の保護を提供します。オリジンヘッダーの検証、Fetch Metadata のチェック、初回ログイン時の保護が含まれます。
設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
disableCSRFCheck: false, // Default: false (keep enabled)
},
});
テスト時、または代替の CSRF 対策を使用する場合にのみ無効にしてください。
信頼するオリジン
信頼するオリジンの設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
baseURL: "https://api.example.com",
trustedOrigins: [
"https://app.example.com",
"https://admin.example.com",
],
});
baseURL のオリジンは自動的に信頼されます。環境変数でも設定できます:BETTER_AUTH_TRUSTED_ORIGINS=https://app.example.com,https://admin.example.com
ワイルドカードパターン
trustedOrigins: [
"*.example.com", // Matches any subdomain
"https://*.example.com", // Protocol-specific wildcard
"exp://192.168.*.*:*/*", // Custom schemes (e.g., Expo)
]
動的な信頼するオリジン
リクエストに基づいて信頼するオリジンを計算します。
trustedOrigins: async (request) => {
// Validate against database, header, etc.
const tenant = getTenantFromRequest(request);
return [`https://${tenant}.myapp.com`];
}
callbackURL、redirectTo、errorCallbackURL、newUserCallbackURL、origin を信頼するオリジンに照らして検証します。無効な URL には 403 を返します。
セッションのセキュリティ
セッションの有効期限
import { betterAuth } from "better-auth";
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days (default)
updateAge: 60 * 60 * 24, // Refresh session every 24 hours (default)
},
});
セッションのキャッシュ戦略
データベースへのクエリを減らすため、セッションデータを Cookie にキャッシュします。
session: {
cookieCache: {
enabled: true,
maxAge: 60 * 5, // 5 minutes
strategy: "compact", // Options: "compact", "jwt", "jwe"
},
}
戦略:"compact"(Base64url + HMAC。最小サイズ)、"jwt"(HS256。標準)、"jwe"(暗号化。セッションに機密データが含まれる場合に使用)。
Cookie のセキュリティ
デフォルト設定:secure: true(HTTPS/本番環境)、sameSite: "lax"、httpOnly: true、path: "/"、プレフィックス __Secure-。
カスタム Cookie の設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
useSecureCookies: true, // Force secure cookies
cookiePrefix: "myapp", // Custom prefix (default: "better-auth")
defaultCookieAttributes: {
sameSite: "strict", // Stricter CSRF protection
path: "/auth", // Limit cookie scope
},
},
});
サブドメイン間での Cookie の共有
advanced: {
crossSubDomainCookies: {
enabled: true,
domain: ".example.com", // Note the leading dot
additionalCookies: ["session_token", "session_data"],
},
}
認証の共有が必要で、すべてのサブドメインを信頼できる場合にのみ有効にしてください。
OAuth/ソーシャルプロバイダーのセキュリティ
すべての OAuth フローで PKCE が自動的に有効になります。State トークンは 32 文字のランダムな文字列で、10 分後に期限切れになります。
State パラメーターの保存
import { betterAuth } from "better-auth";
export const auth = betterAuth({
account: {
storeStateStrategy: "cookie", // Options: "cookie" (default), "database"
},
});
OAuth トークンの暗号化
account: {
encryptOAuthTokens: true, // Uses AES-256-GCM
}
ユーザーに代わって API にアクセスするために OAuth トークンを保存する場合は、有効にしてください。Cookie を維持できないモバイルアプリに限り、skipStateCookieCheck: true を使用してください。
IP ベースのセキュリティ
IP アドレスの設定
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
ipAddress: {
ipAddressHeaders: ["x-forwarded-for", "x-real-ip"], // Headers to check
disableIpTracking: false, // Keep enabled for rate limiting
},
},
});
ipv6Subnet(128、64、48、32。デフォルトは 64)を設定して、IPv6 アドレスをグループ化します。信頼できるリバースプロキシの背後にある場合にのみ、trustedProxyHeaders: true を有効にしてください。
セキュリティ監査のためのデータベースフック
import { betterAuth } from "better-auth";
export const auth = betterAuth({
databaseHooks: {
session: {
create: {
after: async ({ data, ctx }) => {
await auditLog("session.created", {
userId: data.userId,
ip: ctx?.request?.headers.get("x-forwarded-for"),
userAgent: ctx?.request?.headers.get("user-agent"),
});
},
},
delete: {
before: async ({ data }) => {
await auditLog("session.revoked", { sessionId: data.id });
},
},
},
user: {
update: {
after: async ({ data, oldData }) => {
if (oldData?.email !== data.email) {
await auditLog("user.email_changed", {
userId: data.id,
oldEmail: oldData?.email,
newEmail: data.email,
});
}
},
},
},
account: {
create: {
after: async ({ data }) => {
await auditLog("account.linked", {
userId: data.userId,
provider: data.providerId,
});
},
},
},
},
});
before フックから false を返すと、操作を防止できます。
バックグラウンドタスク
import { betterAuth } from "better-auth";
export const auth = betterAuth({
advanced: {
backgroundTasks: {
handler: (promise) => {
// Platform-specific handler
// Vercel: waitUntil(promise)
// Cloudflare: ctx.waitUntil(promise)
waitUntil(promise);
},
},
},
});
メールの送信などの操作がレスポンスのタイミングに影響しないようにします。
アカウント列挙の防止
組み込み機能:一貫したレスポンスメッセージ、無効なリクエストに対するダミー操作、バックグラウンドでのメール送信。具体的なメッセージ(「ユーザーが見つかりません」など)ではなく、汎用的なエラーメッセージ(「認証情報が無効です」など)を返してください。
セキュリティ設定の完全な例
import { betterAuth } from "better-auth";
export const auth = betterAuth({
secret: process.env.BETTER_AUTH_SECRET,
baseURL: "https://api.example.com",
trustedOrigins: [
"https://app.example.com",
"https://*.preview.example.com",
],
// Rate limiting
rateLimit: {
enabled: true,
storage: "secondary-storage",
customRules: {
"/api/auth/sign-in/email": { window: 60, max: 5 },
"/api/auth/sign-up/email": { window: 60, max: 3 },
},
},
// Session security
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days
updateAge: 60 * 60 * 24, // 24 hours
freshAge: 60 * 60, // 1 hour for sensitive actions
cookieCache: {
enabled: true,
maxAge: 300,
strategy: "jwe", // Encrypted session data
},
},
// OAuth security
account: {
encryptOAuthTokens: true,
storeStateStrategy: "cookie",
},
// Advanced settings
advanced: {
useSecureCookies: true,
cookiePrefix: "myapp",
defaultCookieAttributes: {
sameSite: "lax",
},
ipAddress: {
ipAddressHeaders: ["x-forwarded-for"],
ipv6Subnet: 64,
},
backgroundTasks: {
handler: (promise) => waitUntil(promise),
},
},
// Security auditing
databaseHooks: {
session: {
create: {
after: async ({ data, ctx }) => {
console.log(`New session for user ${data.userId}`);
},
},
},
user: {
update: {
after: async ({ data, oldData }) => {
if (oldData?.email !== data.email) {
console.log(`Email changed for user ${data.id}`);
}
},
},
},
},
});
セキュリティチェックリスト
本番環境にデプロイする前に:
- シークレット:強力で一意のシークレットを使用してください(32文字以上で、エントロピーが高いもの)
- HTTPS:
baseURLで HTTPS が使用されていることを確認してください - 信頼できるオリジン:有効なすべてのオリジン(フロントエンド、モバイルアプリ)を設定してください
- レート制限:適切な制限を設定して有効にしたままにしてください
- CSRF 保護:有効にしたままにしてください(
disableCSRFCheck: false) - セキュア Cookie:HTTPS を使用すると自動的に有効になります
- OAuth トークン:トークンを保存する場合は、
encryptOAuthTokens: trueを検討してください - バックグラウンドタスク:サーバーレスプラットフォーム向けに設定してください
- 監査ログ:
databaseHooksまたはhooksを使用して実装してください - IP トラッキング:プロキシの背後にある場合は、ヘッダーを設定してください