一键导入
email-and-password-best-practices
This skill provides guidance and enforcement rules for implementing secure email and password authentication using Better Auth.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
This skill provides guidance and enforcement rules for implementing secure email and password authentication using Better Auth.
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
Verify Linear backlog tickets against the actual codebase before anyone implements them — extract each ticket's checkable claims, grep the code to confirm or refute, classify by scope, and leave an evidence-backed triage comment. Use when the user says "triage the backlog", "comb through Linear tasks", "verify these tickets", "/linear-triage", or on a schedule. Not for implementing fixes (use linear-to-pr on tickets triaged valid), and never changes ticket state beyond commenting unless explicitly told to.
Verify any UI-affecting change end-to-end before declaring it done — boot the app, interact with the change via Playwright, capture before/after screenshots (light/dark, desktop/phone), check console errors, and attach the evidence. Use after implementing or reviewing any change to components, routes with visible output, layouts, styling, or theming. Triggers include "verify this UI change", "did the banner/button/layout change work", or finishing any *.tsx edit that alters rendered markup or classes. Not for server-only changes (models, API routes, env plumbing), docs, or pure test edits.
Explore the running Iridium app like a curious, destructive user and file GitHub issues for confirmed bugs. Picks an exploration charter (auth, notes, chat, settings, admin, mobile), drives the app with Playwright, reproduces every finding twice, dedupes against open qa-explorer issues, and files structured reports with repro scripts. Use when the user says "qa sweep", "explore the app for bugs", "run the qa explorer", "hunt for bugs", or "do some exploratory testing". Not for implementing fixes (use github-issue-to-pr on the filed issues), not for deterministic regression tests (those live in tests/), and never against production.
Drive a GitHub issue to an open pull request. Fetches the issue with gh, self-assigns, creates a linked branch via gh issue develop, implements the change, runs validation plus targeted Playwright e2e tests, captures screenshots of UI changes and links them from the PR body, and opens the PR with a closing "Fixes
Drive a Linear issue to an open GitHub pull request. Fetches the issue, branches using Linear's suggested branch name, implements the change, runs validation plus targeted Playwright e2e tests, captures screenshots of UI changes and links them from the PR body, opens the PR with gh, and moves the issue to a review status. Use when the user says "work on ABC-123" (any Linear issue identifier), "take this Linear issue to a PR", "implement this ticket", "linear to pr", or pastes a linear.app issue URL to be implemented. Not for creating or triaging Linear issues, and not for reviewing or merging existing PRs.
MANDATORY color usage rules for daisyUI 5
| name | email-and-password-best-practices |
| description | This skill provides guidance and enforcement rules for implementing secure email and password authentication using Better Auth. |
When enabling email/password authentication, configure emailVerification.sendVerificationEmail to verify user email addresses. This helps prevent fake sign-ups and ensures users have access to the email they registered with.
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url, token }, request) => {
await sendEmail({
to: user.email,
subject: 'Verify your email address',
text: `Click the link to verify your email: ${url}`,
});
},
},
});
Note: The url parameter contains the full verification link. The token is available if you need to build a custom verification URL.
For stricter security, enable emailAndPassword.requireEmailVerification to block sign-in until the user verifies their email. When enabled, unverified users will receive a new verification email on each sign-in attempt.
export const auth = betterAuth({
emailAndPassword: {
requireEmailVerification: true,
},
});
Note: This requires sendVerificationEmail to be configured and only applies to email/password sign-ins.
While Better Auth validates inputs server-side, implementing client-side validation is still recommended for two key reasons:
Always use absolute URLs (including the origin) for callback URLs in sign-up and sign-in requests. This prevents Better Auth from needing to infer the origin, which can cause issues when your backend and frontend are on different domains.
const { data, error } = await authClient.signUp.email({
callbackURL: 'https://example.com/callback', // absolute URL with origin
});
Password reset flows are essential to any email/password system, we recommend setting this up.
To allow users to reset a password first you need to provide sendResetPassword function to the email and password authenticator.
import { betterAuth } from 'better-auth';
import { sendEmail } from './email'; // your email sending function
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
// Custom email sending function to send reset-password email
sendResetPassword: async ({ user, url, token }, request) => {
void sendEmail({
to: user.email,
subject: 'Reset your password',
text: `Click the link to reset your password: ${url}`,
});
},
// Optional event hook
onPasswordReset: async ({ user }, request) => {
// your logic here
console.log(`Password for user ${user.email} has been reset.`);
},
},
});
Better Auth implements several security measures in the password reset flow:
runInBackgroundOrAwait internally to send reset emails without blocking the response. This prevents attackers from measuring response times to determine if an email exists."If this email exists in our system, check your email for the reset link" regardless of whether the user exists.On serverless platforms, configure a background task handler to ensure emails are sent reliably:
export const auth = betterAuth({
advanced: {
backgroundTasks: {
handler: (promise) => {
// Use platform-specific methods like waitUntil
waitUntil(promise);
},
},
},
});
generateId(24), producing a 24-character alphanumeric string (a-z, A-Z, 0-9) with high entropy.resetPasswordTokenExpiresIn (in seconds):export const auth = betterAuth({
emailAndPassword: {
enabled: true,
resetPasswordTokenExpiresIn: 60 * 30, // 30 minutes
},
});
Enable revokeSessionsOnPasswordReset to invalidate all existing sessions when a password is reset. This ensures that if an attacker has an active session, it will be terminated:
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
revokeSessionsOnPasswordReset: true,
},
});
The redirectTo parameter is validated against your trustedOrigins configuration to prevent open redirect attacks. Malicious redirect URLs will be rejected with a 403 error.
During password reset, the new password must meet length requirements:
minPasswordLengthmaxPasswordLengthexport const auth = betterAuth({
emailAndPassword: {
enabled: true,
minPasswordLength: 12,
maxPasswordLength: 256,
},
});
Once the password reset configurations are set-up, you can now call the requestPasswordReset function to send reset password link to user. If the user exists, it will trigger the sendResetPassword function you provided in the auth config.
const data = await auth.api.requestPasswordReset({
body: {
email: 'john.doe@example.com', // required
redirectTo: 'https://example.com/reset-password',
},
});
Or authClient:
const { data, error } = await authClient.requestPasswordReset({
email: 'john.doe@example.com', // required
redirectTo: 'https://example.com/reset-password',
});
Note: While the email is required, we also recommend configuring the redirectTo for a smoother user experience.
Better Auth uses scrypt by default for password hashing. This is a solid choice because:
To use a different algorithm (e.g., Argon2id), provide custom hash and verify functions in the emailAndPassword.password configuration:
import { betterAuth } from 'better-auth';
import { hash, verify, type Options } from '@node-rs/argon2';
const argon2Options: Options = {
memoryCost: 65536, // 64 MiB
timeCost: 3, // 3 iterations
parallelism: 4, // 4 parallel lanes
outputLen: 32, // 32 byte output
algorithm: 2, // Argon2id variant
};
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
password: {
hash: (password) => hash(password, argon2Options),
verify: ({ password, hash: storedHash }) =>
verify(storedHash, password, argon2Options),
},
},
});
Note: If you switch hashing algorithms on an existing system, users with passwords hashed using the old algorithm won't be able to sign in. Plan a migration strategy if needed.