add better-auth related agent skills
This commit is contained in:
@@ -0,0 +1,166 @@
|
|||||||
|
---
|
||||||
|
name: better-auth-best-practices
|
||||||
|
description: Skill for integrating Better Auth - the comprehensive TypeScript authentication framework.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Better Auth Integration Guide
|
||||||
|
|
||||||
|
**Always consult [better-auth.com/docs](https://better-auth.com/docs) for code examples and latest API.**
|
||||||
|
|
||||||
|
Better Auth is a TypeScript-first, framework-agnostic auth framework supporting email/password, OAuth, magic links, passkeys, and more via plugins.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Quick Reference
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
- `BETTER_AUTH_SECRET` - Encryption secret (min 32 chars). Generate: `openssl rand -base64 32`
|
||||||
|
- `BETTER_AUTH_URL` - Base URL (e.g., `https://example.com`)
|
||||||
|
|
||||||
|
Only define `baseURL`/`secret` in config if env vars are NOT set.
|
||||||
|
|
||||||
|
### File Location
|
||||||
|
CLI looks for `auth.ts` in: `./`, `./lib`, `./utils`, or under `./src`. Use `--config` for custom path.
|
||||||
|
|
||||||
|
### CLI Commands
|
||||||
|
- `npx @better-auth/cli@latest migrate` - Apply schema (built-in adapter)
|
||||||
|
- `npx @better-auth/cli@latest generate` - Generate schema for Prisma/Drizzle
|
||||||
|
- `npx @better-auth/cli mcp --cursor` - Add MCP to AI tools
|
||||||
|
|
||||||
|
**Re-run after adding/changing plugins.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Core Config Options
|
||||||
|
|
||||||
|
| Option | Notes |
|
||||||
|
|--------|-------|
|
||||||
|
| `appName` | Optional display name |
|
||||||
|
| `baseURL` | Only if `BETTER_AUTH_URL` not set |
|
||||||
|
| `basePath` | Default `/api/auth`. Set `/` for root. |
|
||||||
|
| `secret` | Only if `BETTER_AUTH_SECRET` not set |
|
||||||
|
| `database` | Required for most features. See adapters docs. |
|
||||||
|
| `secondaryStorage` | Redis/KV for sessions & rate limits |
|
||||||
|
| `emailAndPassword` | `{ enabled: true }` to activate |
|
||||||
|
| `socialProviders` | `{ google: { clientId, clientSecret }, ... }` |
|
||||||
|
| `plugins` | Array of plugins |
|
||||||
|
| `trustedOrigins` | CSRF whitelist |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database
|
||||||
|
|
||||||
|
**Direct connections:** Pass `pg.Pool`, `mysql2` pool, `better-sqlite3`, or `bun:sqlite` instance.
|
||||||
|
|
||||||
|
**ORM adapters:** Import from `better-auth/adapters/drizzle`, `better-auth/adapters/prisma`, `better-auth/adapters/mongodb`.
|
||||||
|
|
||||||
|
**Critical:** Better Auth uses adapter model names, NOT underlying table names. If Prisma model is `User` mapping to table `users`, use `modelName: "user"` (Prisma reference), not `"users"`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Session Management
|
||||||
|
|
||||||
|
**Storage priority:**
|
||||||
|
1. If `secondaryStorage` defined → sessions go there (not DB)
|
||||||
|
2. Set `session.storeSessionInDatabase: true` to also persist to DB
|
||||||
|
3. No database + `cookieCache` → fully stateless mode
|
||||||
|
|
||||||
|
**Cookie cache strategies:**
|
||||||
|
- `compact` (default) - Base64url + HMAC. Smallest.
|
||||||
|
- `jwt` - Standard JWT. Readable but signed.
|
||||||
|
- `jwe` - Encrypted. Maximum security.
|
||||||
|
|
||||||
|
**Key options:** `session.expiresIn` (default 7 days), `session.updateAge` (refresh interval), `session.cookieCache.maxAge`, `session.cookieCache.version` (change to invalidate all sessions).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## User & Account Config
|
||||||
|
|
||||||
|
**User:** `user.modelName`, `user.fields` (column mapping), `user.additionalFields`, `user.changeEmail.enabled` (disabled by default), `user.deleteUser.enabled` (disabled by default).
|
||||||
|
|
||||||
|
**Account:** `account.modelName`, `account.accountLinking.enabled`, `account.storeAccountCookie` (for stateless OAuth).
|
||||||
|
|
||||||
|
**Required for registration:** `email` and `name` fields.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Email Flows
|
||||||
|
|
||||||
|
- `emailVerification.sendVerificationEmail` - Must be defined for verification to work
|
||||||
|
- `emailVerification.sendOnSignUp` / `sendOnSignIn` - Auto-send triggers
|
||||||
|
- `emailAndPassword.sendResetPassword` - Password reset email handler
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
**In `advanced`:**
|
||||||
|
- `useSecureCookies` - Force HTTPS cookies
|
||||||
|
- `disableCSRFCheck` - ⚠️ Security risk
|
||||||
|
- `disableOriginCheck` - ⚠️ Security risk
|
||||||
|
- `crossSubDomainCookies.enabled` - Share cookies across subdomains
|
||||||
|
- `ipAddress.ipAddressHeaders` - Custom IP headers for proxies
|
||||||
|
- `database.generateId` - Custom ID generation or `"serial"`/`"uuid"`/`false`
|
||||||
|
|
||||||
|
**Rate limiting:** `rateLimit.enabled`, `rateLimit.window`, `rateLimit.max`, `rateLimit.storage` ("memory" | "database" | "secondary-storage").
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Hooks
|
||||||
|
|
||||||
|
**Endpoint hooks:** `hooks.before` / `hooks.after` - Array of `{ matcher, handler }`. Use `createAuthMiddleware`. Access `ctx.path`, `ctx.context.returned` (after), `ctx.context.session`.
|
||||||
|
|
||||||
|
**Database hooks:** `databaseHooks.user.create.before/after`, same for `session`, `account`. Useful for adding default values or post-creation actions.
|
||||||
|
|
||||||
|
**Hook context (`ctx.context`):** `session`, `secret`, `authCookies`, `password.hash()`/`verify()`, `adapter`, `internalAdapter`, `generateId()`, `tables`, `baseURL`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Plugins
|
||||||
|
|
||||||
|
**Import from dedicated paths for tree-shaking:**
|
||||||
|
```
|
||||||
|
import { twoFactor } from "better-auth/plugins/two-factor"
|
||||||
|
```
|
||||||
|
NOT `from "better-auth/plugins"`.
|
||||||
|
|
||||||
|
**Popular plugins:** `twoFactor`, `organization`, `passkey`, `magicLink`, `emailOtp`, `username`, `phoneNumber`, `admin`, `apiKey`, `bearer`, `jwt`, `multiSession`, `sso`, `oauthProvider`, `oidcProvider`, `openAPI`, `genericOAuth`.
|
||||||
|
|
||||||
|
Client plugins go in `createAuthClient({ plugins: [...] })`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Client
|
||||||
|
|
||||||
|
Import from: `better-auth/client` (vanilla), `better-auth/react`, `better-auth/vue`, `better-auth/svelte`, `better-auth/solid`.
|
||||||
|
|
||||||
|
Key methods: `signUp.email()`, `signIn.email()`, `signIn.social()`, `signOut()`, `useSession()`, `getSession()`, `revokeSession()`, `revokeSessions()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Type Safety
|
||||||
|
|
||||||
|
Infer types: `typeof auth.$Infer.Session`, `typeof auth.$Infer.Session.user`.
|
||||||
|
|
||||||
|
For separate client/server projects: `createAuthClient<typeof auth>()`.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Gotchas
|
||||||
|
|
||||||
|
1. **Model vs table name** - Config uses ORM model name, not DB table name
|
||||||
|
2. **Plugin schema** - Re-run CLI after adding plugins
|
||||||
|
3. **Secondary storage** - Sessions go there by default, not DB
|
||||||
|
4. **Cookie cache** - Custom session fields NOT cached, always re-fetched
|
||||||
|
5. **Stateless mode** - No DB = session in cookie only, logout on cache expiry
|
||||||
|
6. **Change email flow** - Sends to current email first, then new email
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Docs](https://better-auth.com/docs)
|
||||||
|
- [Options Reference](https://better-auth.com/docs/reference/options)
|
||||||
|
- [LLMs.txt](https://better-auth.com/llms.txt)
|
||||||
|
- [GitHub](https://github.com/better-auth/better-auth)
|
||||||
|
- [Init Options Source](https://github.com/better-auth/better-auth/blob/main/packages/core/src/types/init-options.ts)
|
||||||
@@ -0,0 +1,321 @@
|
|||||||
|
---
|
||||||
|
name: create-auth-skill
|
||||||
|
description: Skill for creating auth layers in TypeScript/JavaScript apps using Better Auth.
|
||||||
|
---
|
||||||
|
|
||||||
|
# Create Auth Skill
|
||||||
|
|
||||||
|
Guide for adding authentication to TypeScript/JavaScript applications using Better Auth.
|
||||||
|
|
||||||
|
**For code examples and syntax, see [better-auth.com/docs](https://better-auth.com/docs).**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Planning (REQUIRED before implementation)
|
||||||
|
|
||||||
|
Before writing any code, gather requirements by scanning the project and asking the user structured questions. This ensures the implementation matches their needs.
|
||||||
|
|
||||||
|
### Step 1: Scan the project
|
||||||
|
|
||||||
|
Analyze the codebase to auto-detect:
|
||||||
|
- **Framework** — Look for `next.config`, `svelte.config`, `nuxt.config`, `astro.config`, `vite.config`, or Express/Hono entry files.
|
||||||
|
- **Database/ORM** — Look for `prisma/schema.prisma`, `drizzle.config`, `package.json` deps (`pg`, `mysql2`, `better-sqlite3`, `mongoose`, `mongodb`).
|
||||||
|
- **Existing auth** — Look for existing auth libraries (`next-auth`, `lucia`, `clerk`, `supabase/auth`, `firebase/auth`) in `package.json` or imports.
|
||||||
|
- **Package manager** — Check for `pnpm-lock.yaml`, `yarn.lock`, `bun.lockb`, or `package-lock.json`.
|
||||||
|
|
||||||
|
Use what you find to pre-fill defaults and skip questions you can already answer.
|
||||||
|
|
||||||
|
### Step 2: Ask planning questions
|
||||||
|
|
||||||
|
Use the `AskQuestion` tool to ask the user **all applicable questions in a single call**. Skip any question you already have a confident answer for from the scan. Group them under a title like "Auth Setup Planning".
|
||||||
|
|
||||||
|
**Questions to ask:**
|
||||||
|
|
||||||
|
1. **Project type** (skip if detected)
|
||||||
|
- Prompt: "What type of project is this?"
|
||||||
|
- Options: New project from scratch | Adding auth to existing project | Migrating from another auth library
|
||||||
|
|
||||||
|
2. **Framework** (skip if detected)
|
||||||
|
- Prompt: "Which framework are you using?"
|
||||||
|
- Options: Next.js (App Router) | Next.js (Pages Router) | SvelteKit | Nuxt | Astro | Express | Hono | SolidStart | Other
|
||||||
|
|
||||||
|
3. **Database & ORM** (skip if detected)
|
||||||
|
- Prompt: "Which database setup will you use?"
|
||||||
|
- Options: PostgreSQL (Prisma) | PostgreSQL (Drizzle) | PostgreSQL (pg driver) | MySQL (Prisma) | MySQL (Drizzle) | MySQL (mysql2 driver) | SQLite (Prisma) | SQLite (Drizzle) | SQLite (better-sqlite3 driver) | MongoDB (Mongoose) | MongoDB (native driver)
|
||||||
|
|
||||||
|
4. **Authentication methods** (always ask, allow multiple)
|
||||||
|
- Prompt: "Which sign-in methods do you need?"
|
||||||
|
- Options: Email & password | Social OAuth (Google, GitHub, etc.) | Magic link (passwordless email) | Passkey (WebAuthn) | Phone number
|
||||||
|
- `allow_multiple: true`
|
||||||
|
|
||||||
|
5. **Social providers** (only if they selected Social OAuth above — ask in a follow-up call)
|
||||||
|
- Prompt: "Which social providers do you need?"
|
||||||
|
- Options: Google | GitHub | Apple | Microsoft | Discord | Twitter/X
|
||||||
|
- `allow_multiple: true`
|
||||||
|
|
||||||
|
6. **Email verification** (only if Email & password was selected above — ask in a follow-up call)
|
||||||
|
- Prompt: "Do you want to require email verification?"
|
||||||
|
- Options: Yes | No
|
||||||
|
|
||||||
|
7. **Email provider** (only if email verification is Yes, or if Password reset is selected in features — ask in a follow-up call)
|
||||||
|
- Prompt: "How do you want to send emails?"
|
||||||
|
- Options: Resend | Mock it for now (console.log)
|
||||||
|
|
||||||
|
8. **Features & plugins** (always ask, allow multiple)
|
||||||
|
- Prompt: "Which additional features do you need?"
|
||||||
|
- Options: Two-factor authentication (2FA) | Organizations / teams | Admin dashboard | API bearer tokens | Password reset | None of these
|
||||||
|
- `allow_multiple: true`
|
||||||
|
|
||||||
|
9. **Auth pages** (always ask, allow multiple — pre-select based on earlier answers)
|
||||||
|
- Prompt: "Which auth pages do you need?"
|
||||||
|
- Options vary based on previous answers:
|
||||||
|
- Always available: Sign in | Sign up
|
||||||
|
- If Email & password selected: Forgot password | Reset password
|
||||||
|
- If email verification enabled: Email verification
|
||||||
|
- `allow_multiple: true`
|
||||||
|
|
||||||
|
10. **Auth UI style** (always ask)
|
||||||
|
- Prompt: "What style do you want for the auth pages? Pick one or describe your own."
|
||||||
|
- Options: Minimal & clean | Centered card with background | Split layout (form + hero image) | Floating / glassmorphism | Other (I'll describe)
|
||||||
|
|
||||||
|
### Step 3: Summarize the plan
|
||||||
|
|
||||||
|
After collecting answers, present a concise implementation plan as a markdown checklist. Example:
|
||||||
|
|
||||||
|
```
|
||||||
|
## Auth Implementation Plan
|
||||||
|
|
||||||
|
- **Framework:** Next.js (App Router)
|
||||||
|
- **Database:** PostgreSQL via Prisma
|
||||||
|
- **Auth methods:** Email/password, Google OAuth, GitHub OAuth
|
||||||
|
- **Plugins:** 2FA, Organizations, Email verification
|
||||||
|
- **UI:** Custom forms
|
||||||
|
|
||||||
|
### Steps
|
||||||
|
1. Install `better-auth` and `@better-auth/cli`
|
||||||
|
2. Create `lib/auth.ts` with server config
|
||||||
|
3. Create `lib/auth-client.ts` with React client
|
||||||
|
4. Set up route handler at `app/api/auth/[...all]/route.ts`
|
||||||
|
5. Configure Prisma adapter and generate schema
|
||||||
|
6. Add Google & GitHub OAuth providers
|
||||||
|
7. Enable `twoFactor` and `organization` plugins
|
||||||
|
8. Set up email verification handler
|
||||||
|
9. Run migrations
|
||||||
|
10. Create sign-in / sign-up pages
|
||||||
|
```
|
||||||
|
|
||||||
|
Ask the user to confirm the plan before proceeding to Phase 2.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: Implementation
|
||||||
|
|
||||||
|
Only proceed here after the user confirms the plan from Phase 1.
|
||||||
|
|
||||||
|
Follow the decision tree below, guided by the answers collected above.
|
||||||
|
|
||||||
|
```
|
||||||
|
Is this a new/empty project?
|
||||||
|
├─ YES → New project setup
|
||||||
|
│ 1. Install better-auth (+ scoped packages per plan)
|
||||||
|
│ 2. Create auth.ts with all planned config
|
||||||
|
│ 3. Create auth-client.ts with framework client
|
||||||
|
│ 4. Set up route handler
|
||||||
|
│ 5. Set up environment variables
|
||||||
|
│ 6. Run CLI migrate/generate
|
||||||
|
│ 7. Add plugins from plan
|
||||||
|
│ 8. Create auth UI pages
|
||||||
|
│
|
||||||
|
├─ MIGRATING → Migration from existing auth
|
||||||
|
│ 1. Audit current auth for gaps
|
||||||
|
│ 2. Plan incremental migration
|
||||||
|
│ 3. Install better-auth alongside existing auth
|
||||||
|
│ 4. Migrate routes, then session logic, then UI
|
||||||
|
│ 5. Remove old auth library
|
||||||
|
│ 6. See migration guides in docs
|
||||||
|
│
|
||||||
|
└─ ADDING → Add auth to existing project
|
||||||
|
1. Analyze project structure
|
||||||
|
2. Install better-auth
|
||||||
|
3. Create auth config matching plan
|
||||||
|
4. Add route handler
|
||||||
|
5. Run schema migrations
|
||||||
|
6. Integrate into existing pages
|
||||||
|
7. Add planned plugins and features
|
||||||
|
```
|
||||||
|
|
||||||
|
At the end of implementation, guide users thoroughly on remaining next steps (e.g., setting up OAuth app credentials, deploying env vars, testing flows).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
**Core:** `npm install better-auth`
|
||||||
|
|
||||||
|
**Scoped packages (as needed):**
|
||||||
|
| Package | Use case |
|
||||||
|
|---------|----------|
|
||||||
|
| `@better-auth/passkey` | WebAuthn/Passkey auth |
|
||||||
|
| `@better-auth/sso` | SAML/OIDC enterprise SSO |
|
||||||
|
| `@better-auth/stripe` | Stripe payments |
|
||||||
|
| `@better-auth/scim` | SCIM user provisioning |
|
||||||
|
| `@better-auth/expo` | React Native/Expo |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Environment Variables
|
||||||
|
|
||||||
|
```env
|
||||||
|
BETTER_AUTH_SECRET=<32+ chars, generate with: openssl rand -base64 32>
|
||||||
|
BETTER_AUTH_URL=http://localhost:3000
|
||||||
|
DATABASE_URL=<your database connection string>
|
||||||
|
```
|
||||||
|
|
||||||
|
Add OAuth secrets as needed: `GITHUB_CLIENT_ID`, `GITHUB_CLIENT_SECRET`, `GOOGLE_CLIENT_ID`, etc.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Server Config (auth.ts)
|
||||||
|
|
||||||
|
**Location:** `lib/auth.ts` or `src/lib/auth.ts`
|
||||||
|
|
||||||
|
**Minimal config needs:**
|
||||||
|
- `database` - Connection or adapter
|
||||||
|
- `emailAndPassword: { enabled: true }` - For email/password auth
|
||||||
|
|
||||||
|
**Standard config adds:**
|
||||||
|
- `socialProviders` - OAuth providers (google, github, etc.)
|
||||||
|
- `emailVerification.sendVerificationEmail` - Email verification handler
|
||||||
|
- `emailAndPassword.sendResetPassword` - Password reset handler
|
||||||
|
|
||||||
|
**Full config adds:**
|
||||||
|
- `plugins` - Array of feature plugins
|
||||||
|
- `session` - Expiry, cookie cache settings
|
||||||
|
- `account.accountLinking` - Multi-provider linking
|
||||||
|
- `rateLimit` - Rate limiting config
|
||||||
|
|
||||||
|
**Export types:** `export type Session = typeof auth.$Infer.Session`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Client Config (auth-client.ts)
|
||||||
|
|
||||||
|
**Import by framework:**
|
||||||
|
| Framework | Import |
|
||||||
|
|-----------|--------|
|
||||||
|
| React/Next.js | `better-auth/react` |
|
||||||
|
| Vue | `better-auth/vue` |
|
||||||
|
| Svelte | `better-auth/svelte` |
|
||||||
|
| Solid | `better-auth/solid` |
|
||||||
|
| Vanilla JS | `better-auth/client` |
|
||||||
|
|
||||||
|
**Client plugins** go in `createAuthClient({ plugins: [...] })`.
|
||||||
|
|
||||||
|
**Common exports:** `signIn`, `signUp`, `signOut`, `useSession`, `getSession`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Route Handler Setup
|
||||||
|
|
||||||
|
| Framework | File | Handler |
|
||||||
|
|-----------|------|---------|
|
||||||
|
| Next.js App Router | `app/api/auth/[...all]/route.ts` | `toNextJsHandler(auth)` → export `{ GET, POST }` |
|
||||||
|
| Next.js Pages | `pages/api/auth/[...all].ts` | `toNextJsHandler(auth)` → default export |
|
||||||
|
| Express | Any file | `app.all("/api/auth/*", toNodeHandler(auth))` |
|
||||||
|
| SvelteKit | `src/hooks.server.ts` | `svelteKitHandler(auth)` |
|
||||||
|
| SolidStart | Route file | `solidStartHandler(auth)` |
|
||||||
|
| Hono | Route file | `auth.handler(c.req.raw)` |
|
||||||
|
|
||||||
|
**Next.js Server Components:** Add `nextCookies()` plugin to auth config.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Migrations
|
||||||
|
|
||||||
|
| Adapter | Command |
|
||||||
|
|---------|---------|
|
||||||
|
| Built-in Kysely | `npx @better-auth/cli@latest migrate` (applies directly) |
|
||||||
|
| Prisma | `npx @better-auth/cli@latest generate --output prisma/schema.prisma` then `npx prisma migrate dev` |
|
||||||
|
| Drizzle | `npx @better-auth/cli@latest generate --output src/db/auth-schema.ts` then `npx drizzle-kit push` |
|
||||||
|
|
||||||
|
**Re-run after adding plugins.**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Database Adapters
|
||||||
|
|
||||||
|
| Database | Setup |
|
||||||
|
|----------|-------|
|
||||||
|
| SQLite | Pass `better-sqlite3` or `bun:sqlite` instance directly |
|
||||||
|
| PostgreSQL | Pass `pg.Pool` instance directly |
|
||||||
|
| MySQL | Pass `mysql2` pool directly |
|
||||||
|
| Prisma | `prismaAdapter(prisma, { provider: "postgresql" })` from `better-auth/adapters/prisma` |
|
||||||
|
| Drizzle | `drizzleAdapter(db, { provider: "pg" })` from `better-auth/adapters/drizzle` |
|
||||||
|
| MongoDB | `mongodbAdapter(db)` from `better-auth/adapters/mongodb` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Common Plugins
|
||||||
|
|
||||||
|
| Plugin | Server Import | Client Import | Purpose |
|
||||||
|
|--------|---------------|---------------|---------|
|
||||||
|
| `twoFactor` | `better-auth/plugins` | `twoFactorClient` | 2FA with TOTP/OTP |
|
||||||
|
| `organization` | `better-auth/plugins` | `organizationClient` | Teams/orgs |
|
||||||
|
| `admin` | `better-auth/plugins` | `adminClient` | User management |
|
||||||
|
| `bearer` | `better-auth/plugins` | - | API token auth |
|
||||||
|
| `openAPI` | `better-auth/plugins` | - | API docs |
|
||||||
|
| `passkey` | `@better-auth/passkey` | `passkeyClient` | WebAuthn |
|
||||||
|
| `sso` | `@better-auth/sso` | - | Enterprise SSO |
|
||||||
|
|
||||||
|
**Plugin pattern:** Server plugin + client plugin + run migrations.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auth UI Implementation
|
||||||
|
|
||||||
|
**Sign in flow:**
|
||||||
|
1. `signIn.email({ email, password })` or `signIn.social({ provider, callbackURL })`
|
||||||
|
2. Handle `error` in response
|
||||||
|
3. Redirect on success
|
||||||
|
|
||||||
|
**Session check (client):** `useSession()` hook returns `{ data: session, isPending }`
|
||||||
|
|
||||||
|
**Session check (server):** `auth.api.getSession({ headers: await headers() })`
|
||||||
|
|
||||||
|
**Protected routes:** Check session, redirect to `/sign-in` if null.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Security Checklist
|
||||||
|
|
||||||
|
- [ ] `BETTER_AUTH_SECRET` set (32+ chars)
|
||||||
|
- [ ] `advanced.useSecureCookies: true` in production
|
||||||
|
- [ ] `trustedOrigins` configured
|
||||||
|
- [ ] Rate limits enabled
|
||||||
|
- [ ] Email verification enabled
|
||||||
|
- [ ] Password reset implemented
|
||||||
|
- [ ] 2FA for sensitive apps
|
||||||
|
- [ ] CSRF protection NOT disabled
|
||||||
|
- [ ] `account.accountLinking` reviewed
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Troubleshooting
|
||||||
|
|
||||||
|
| Issue | Fix |
|
||||||
|
|-------|-----|
|
||||||
|
| "Secret not set" | Add `BETTER_AUTH_SECRET` env var |
|
||||||
|
| "Invalid Origin" | Add domain to `trustedOrigins` |
|
||||||
|
| Cookies not setting | Check `baseURL` matches domain; enable secure cookies in prod |
|
||||||
|
| OAuth callback errors | Verify redirect URIs in provider dashboard |
|
||||||
|
| Type errors after adding plugin | Re-run CLI generate/migrate |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Resources
|
||||||
|
|
||||||
|
- [Docs](https://better-auth.com/docs)
|
||||||
|
- [Examples](https://github.com/better-auth/examples)
|
||||||
|
- [Plugins](https://better-auth.com/docs/concepts/plugins)
|
||||||
|
- [CLI](https://better-auth.com/docs/concepts/cli)
|
||||||
|
- [Migration Guides](https://better-auth.com/docs/guides)
|
||||||
@@ -0,0 +1,224 @@
|
|||||||
|
---
|
||||||
|
name: email-and-password-best-practices
|
||||||
|
description: This skill provides guidance and enforcement rules for implementing secure email and password authentication using Better Auth.
|
||||||
|
---
|
||||||
|
|
||||||
|
## Email Verification Setup
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
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.
|
||||||
|
|
||||||
|
### Requiring Email Verification
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const auth = betterAuth({
|
||||||
|
emailAndPassword: {
|
||||||
|
requireEmailVerification: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note**: This requires `sendVerificationEmail` to be configured and only applies to email/password sign-ins.
|
||||||
|
|
||||||
|
## Client side validation
|
||||||
|
|
||||||
|
While Better Auth validates inputs server-side, implementing client-side validation is still recommended for two key reasons:
|
||||||
|
|
||||||
|
1. **Improved UX**: Users receive immediate feedback when inputs don't meet requirements, rather than waiting for a server round-trip.
|
||||||
|
2. **Reduced server load**: Invalid requests are caught early, minimizing unnecessary network traffic to your auth server.
|
||||||
|
|
||||||
|
## Callback URLs
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const { data, error } = await authClient.signUp.email({
|
||||||
|
callbackURL: "https://example.com/callback", // absolute URL with origin
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
## Password Reset Flows
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
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.`);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Security considerations
|
||||||
|
|
||||||
|
Better Auth implements several security measures in the password reset flow:
|
||||||
|
|
||||||
|
#### Timing attack prevention
|
||||||
|
|
||||||
|
- **Background email sending**: Better Auth uses `runInBackgroundOrAwait` internally to send reset emails without blocking the response. This prevents attackers from measuring response times to determine if an email exists.
|
||||||
|
- **Dummy operations on invalid requests**: When a user is not found, Better Auth still performs token generation and a database lookup (with a dummy value) to maintain consistent response times.
|
||||||
|
- **Constant response message**: The API always returns `"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:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const auth = betterAuth({
|
||||||
|
advanced: {
|
||||||
|
backgroundTasks: {
|
||||||
|
handler: (promise) => {
|
||||||
|
// Use platform-specific methods like waitUntil
|
||||||
|
waitUntil(promise);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Token security
|
||||||
|
|
||||||
|
- **Cryptographically random tokens**: Reset tokens are generated using `generateId(24)`, producing a 24-character alphanumeric string (a-z, A-Z, 0-9) with high entropy.
|
||||||
|
- **Token expiration**: Tokens expire after **1 hour** by default. Configure with `resetPasswordTokenExpiresIn` (in seconds):
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const auth = betterAuth({
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
resetPasswordTokenExpiresIn: 60 * 30, // 30 minutes
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Single-use tokens**: Tokens are deleted immediately after successful password reset, preventing reuse.
|
||||||
|
|
||||||
|
#### Session revocation
|
||||||
|
|
||||||
|
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:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const auth = betterAuth({
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
revokeSessionsOnPasswordReset: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Redirect URL validation
|
||||||
|
|
||||||
|
The `redirectTo` parameter is validated against your `trustedOrigins` configuration to prevent open redirect attacks. Malicious redirect URLs will be rejected with a 403 error.
|
||||||
|
|
||||||
|
#### Password requirements
|
||||||
|
|
||||||
|
During password reset, the new password must meet length requirements:
|
||||||
|
- **Minimum**: 8 characters (default), configurable via `minPasswordLength`
|
||||||
|
- **Maximum**: 128 characters (default), configurable via `maxPasswordLength`
|
||||||
|
|
||||||
|
```ts
|
||||||
|
export const auth = betterAuth({
|
||||||
|
emailAndPassword: {
|
||||||
|
enabled: true,
|
||||||
|
minPasswordLength: 12,
|
||||||
|
maxPasswordLength: 256,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending the password reset
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
```ts
|
||||||
|
const data = await auth.api.requestPasswordReset({
|
||||||
|
body: {
|
||||||
|
email: "john.doe@example.com", // required
|
||||||
|
redirectTo: "https://example.com/reset-password",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
Or authClient:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Password Hashing
|
||||||
|
|
||||||
|
Better Auth uses `scrypt` by default for password hashing. This is a solid choice because:
|
||||||
|
|
||||||
|
- It's designed to be slow and memory-intensive, making brute-force attacks costly
|
||||||
|
- It's natively supported by Node.js (no external dependencies)
|
||||||
|
- OWASP recommends it when Argon2id isn't available
|
||||||
|
|
||||||
|
### Custom Hashing Algorithm
|
||||||
|
|
||||||
|
To use a different algorithm (e.g., Argon2id), provide custom `hash` and `verify` functions in the `emailAndPassword.password` configuration:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
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.
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"version": 1,
|
||||||
|
"skills": {
|
||||||
|
"better-auth-best-practices": {
|
||||||
|
"source": "better-auth/skills",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "a24359053259e55a75972cab4bf0b0e37e1393bac5f3756ec2e3a7b4b3778d2a"
|
||||||
|
},
|
||||||
|
"create-auth-skill": {
|
||||||
|
"source": "better-auth/skills",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "63689cc54dad93d286af948ab1792e755e7063541ffe90e248eed6523b0748a0"
|
||||||
|
},
|
||||||
|
"email-and-password-best-practices": {
|
||||||
|
"source": "better-auth/skills",
|
||||||
|
"sourceType": "github",
|
||||||
|
"computedHash": "9105cc29a462e8d07da45bbdbc90c3a60be3ba32fbd9d99d49944f5b971f8f96"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user