Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 08:48:52 +08:00
commit 6ec3196ecc
434 changed files with 125248 additions and 0 deletions

View File

@@ -0,0 +1,553 @@
# Advanced Features
Better Auth plugins extend functionality beyond basic authentication.
## Two-Factor Authentication
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { twoFactor } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
twoFactor({
issuer: "YourAppName", // TOTP issuer name
otpOptions: {
period: 30, // OTP validity period (seconds)
digits: 6, // OTP length
}
})
]
});
```
### Client Setup
```ts
import { createAuthClient } from "better-auth/client";
import { twoFactorClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [
twoFactorClient({
twoFactorPage: "/two-factor", // Redirect to 2FA verification page
redirect: true // Auto-redirect if 2FA required
})
]
});
```
### Enable 2FA for User
```ts
// Enable TOTP
const { data } = await authClient.twoFactor.enable({
password: "userPassword" // Verify user identity
});
// data contains QR code URI for authenticator app
const qrCodeUri = data.totpURI;
const backupCodes = data.backupCodes; // Save these securely
```
### Verify TOTP Code
```ts
await authClient.twoFactor.verifyTOTP({
code: "123456",
trustDevice: true // Skip 2FA on this device for 30 days
});
```
### Disable 2FA
```ts
await authClient.twoFactor.disable({
password: "userPassword"
});
```
### Backup Codes
```ts
// Generate new backup codes
const { data } = await authClient.twoFactor.generateBackupCodes({
password: "userPassword"
});
// Use backup code instead of TOTP
await authClient.twoFactor.verifyBackupCode({
code: "backup-code-123"
});
```
## Passkeys (WebAuthn)
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { passkey } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
passkey({
rpName: "YourApp", // Relying Party name
rpID: "yourdomain.com" // Your domain
})
]
});
```
### Client Setup
```ts
import { createAuthClient } from "better-auth/client";
import { passkeyClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [passkeyClient()]
});
```
### Register Passkey
```ts
// User must be authenticated first
await authClient.passkey.register({
name: "My Laptop" // Optional: name for this passkey
});
```
### Sign In with Passkey
```ts
await authClient.passkey.signIn();
```
### List User Passkeys
```ts
const { data } = await authClient.passkey.list();
// data contains array of registered passkeys
```
### Delete Passkey
```ts
await authClient.passkey.delete({
id: "passkey-id"
});
```
## Magic Link
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { magicLink } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
magicLink({
sendMagicLink: async ({ email, url, token }) => {
await sendEmail({
to: email,
subject: "Sign in to YourApp",
html: `Click <a href="${url}">here</a> to sign in.`
});
},
expiresIn: 300, // Link expires in 5 minutes (seconds)
})
]
});
```
### Client Setup
```ts
import { createAuthClient } from "better-auth/client";
import { magicLinkClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [magicLinkClient()]
});
```
### Send Magic Link
```ts
await authClient.magicLink.sendMagicLink({
email: "user@example.com",
callbackURL: "/dashboard"
});
```
### Verify Magic Link
```ts
// Called automatically when user clicks link
// Token in URL query params handled by Better Auth
await authClient.magicLink.verify({
token: "token-from-url"
});
```
## Organizations (Multi-Tenancy)
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { organization } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
organization({
allowUserToCreateOrganization: true,
organizationLimit: 5, // Max orgs per user
creatorRole: "owner" // Role for org creator
})
]
});
```
### Client Setup
```ts
import { createAuthClient } from "better-auth/client";
import { organizationClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [organizationClient()]
});
```
### Create Organization
```ts
await authClient.organization.create({
name: "Acme Corp",
slug: "acme", // Unique slug
metadata: {
industry: "Technology"
}
});
```
### Invite Members
```ts
await authClient.organization.inviteMember({
organizationId: "org-id",
email: "user@example.com",
role: "member", // owner, admin, member
message: "Join our team!" // Optional
});
```
### Accept Invitation
```ts
await authClient.organization.acceptInvitation({
invitationId: "invitation-id"
});
```
### List Organizations
```ts
const { data } = await authClient.organization.list();
// Returns user's organizations
```
### Update Member Role
```ts
await authClient.organization.updateMemberRole({
organizationId: "org-id",
userId: "user-id",
role: "admin"
});
```
### Remove Member
```ts
await authClient.organization.removeMember({
organizationId: "org-id",
userId: "user-id"
});
```
### Delete Organization
```ts
await authClient.organization.delete({
organizationId: "org-id"
});
```
## Session Management
### Configure Session Expiration
```ts
export const auth = betterAuth({
session: {
expiresIn: 60 * 60 * 24 * 7, // 7 days (seconds)
updateAge: 60 * 60 * 24, // Update session every 24 hours
cookieCache: {
enabled: true,
maxAge: 5 * 60 // Cache for 5 minutes
}
}
});
```
### Server-Side Session
```ts
// Next.js
import { auth } from "@/lib/auth";
import { headers } from "next/headers";
const session = await auth.api.getSession({
headers: await headers()
});
if (!session) {
// Not authenticated
}
```
### Client-Side Session
```tsx
// React
import { authClient } from "@/lib/auth-client";
function UserProfile() {
const { data: session, isPending, error } = authClient.useSession();
if (isPending) return <div>Loading...</div>;
if (error) return <div>Error</div>;
if (!session) return <div>Not logged in</div>;
return <div>Hello, {session.user.name}!</div>;
}
```
### List Active Sessions
```ts
const { data: sessions } = await authClient.listSessions();
// Returns all active sessions for current user
```
### Revoke Session
```ts
await authClient.revokeSession({
sessionId: "session-id"
});
```
### Revoke All Sessions
```ts
await authClient.revokeAllSessions();
```
## Rate Limiting
### Server Configuration
```ts
export const auth = betterAuth({
rateLimit: {
enabled: true,
window: 60, // Time window in seconds
max: 10, // Max requests per window
storage: "memory", // "memory" or "database"
customRules: {
"/api/auth/sign-in": {
window: 60,
max: 5 // Stricter limit for sign-in
},
"/api/auth/sign-up": {
window: 3600,
max: 3 // 3 signups per hour
}
}
}
});
```
### Custom Rate Limiter
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
rateLimit: {
enabled: true,
customLimiter: async ({ request, limit }) => {
// Custom rate limiting logic
const ip = request.headers.get("x-forwarded-for");
const key = `ratelimit:${ip}`;
// Use Redis, etc.
const count = await redis.incr(key);
if (count === 1) {
await redis.expire(key, limit.window);
}
if (count > limit.max) {
throw new Error("Rate limit exceeded");
}
}
}
});
```
## Anonymous Sessions
Track users before they sign up.
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { anonymous } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [anonymous()]
});
```
### Client Usage
```ts
// Create anonymous session
const { data } = await authClient.signIn.anonymous();
// Convert to full account
await authClient.signUp.email({
email: "user@example.com",
password: "password123",
linkAnonymousSession: true // Link anonymous data
});
```
## Email OTP
One-time password via email (passwordless).
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { emailOTP } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
emailOTP({
sendVerificationOTP: async ({ email, otp }) => {
await sendEmail({
to: email,
subject: "Your verification code",
text: `Your code is: ${otp}`
});
},
expiresIn: 300, // 5 minutes
length: 6 // OTP length
})
]
});
```
### Client Usage
```ts
// Send OTP to email
await authClient.emailOTP.sendOTP({
email: "user@example.com"
});
// Verify OTP
await authClient.emailOTP.verifyOTP({
email: "user@example.com",
otp: "123456"
});
```
## Phone Number Authentication
Requires phone number plugin.
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { phoneNumber } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
phoneNumber({
sendOTP: async ({ phoneNumber, otp }) => {
// Use Twilio, AWS SNS, etc.
await sendSMS(phoneNumber, `Your code: ${otp}`);
}
})
]
});
```
### Client Usage
```ts
// Sign up with phone
await authClient.signUp.phoneNumber({
phoneNumber: "+1234567890",
password: "password123"
});
// Send OTP
await authClient.phoneNumber.sendOTP({
phoneNumber: "+1234567890"
});
// Verify OTP
await authClient.phoneNumber.verifyOTP({
phoneNumber: "+1234567890",
otp: "123456"
});
```
## Best Practices
1. **2FA**: Offer 2FA as optional, make mandatory for admin users
2. **Passkeys**: Implement as progressive enhancement (fallback to password)
3. **Magic Links**: Set short expiration (5-15 minutes)
4. **Organizations**: Implement RBAC for org permissions
5. **Sessions**: Use short expiration for sensitive apps
6. **Rate Limiting**: Enable in production, adjust limits based on usage
7. **Anonymous Sessions**: Clean up old anonymous sessions periodically
8. **Backup Codes**: Force users to save backup codes before enabling 2FA
9. **Multi-Device**: Allow users to manage trusted devices
10. **Audit Logs**: Track sensitive operations (role changes, 2FA changes)
## Regenerate Schema After Plugins
After adding any plugin:
```bash
npx @better-auth/cli generate
npx @better-auth/cli migrate # if using Kysely
```
Or manually apply migrations for your ORM (Drizzle, Prisma).

View File

@@ -0,0 +1,577 @@
# Database Integration
Better Auth supports multiple databases and ORMs for flexible data persistence.
## Supported Databases
- SQLite
- PostgreSQL
- MySQL/MariaDB
- MongoDB
- Any database with adapter support
## Direct Database Connection
### SQLite
```ts
import { betterAuth } from "better-auth";
import Database from "better-sqlite3";
export const auth = betterAuth({
database: new Database("./sqlite.db"),
// or
database: new Database(":memory:") // In-memory for testing
});
```
### PostgreSQL
```ts
import { betterAuth } from "better-auth";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
// or explicit config
host: "localhost",
port: 5432,
user: "postgres",
password: "password",
database: "myapp"
});
export const auth = betterAuth({
database: pool
});
```
### MySQL
```ts
import { betterAuth } from "better-auth";
import { createPool } from "mysql2/promise";
const pool = createPool({
host: "localhost",
user: "root",
password: "password",
database: "myapp",
waitForConnections: true,
connectionLimit: 10
});
export const auth = betterAuth({
database: pool
});
```
## ORM Adapters
### Drizzle ORM
**Install:**
```bash
npm install drizzle-orm better-auth
```
**Setup:**
```ts
import { betterAuth } from "better-auth";
import { drizzleAdapter } from "better-auth/adapters/drizzle";
import { drizzle } from "drizzle-orm/node-postgres";
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL
});
const db = drizzle(pool);
export const auth = betterAuth({
database: drizzleAdapter(db, {
provider: "pg", // "pg" | "mysql" | "sqlite"
schema: {
// Optional: custom table names
user: "users",
session: "sessions",
account: "accounts",
verification: "verifications"
}
})
});
```
**Generate Schema:**
```bash
npx @better-auth/cli generate --adapter drizzle
```
### Prisma
**Install:**
```bash
npm install @prisma/client better-auth
```
**Setup:**
```ts
import { betterAuth } from "better-auth";
import { prismaAdapter } from "better-auth/adapters/prisma";
import { PrismaClient } from "@prisma/client";
const prisma = new PrismaClient();
export const auth = betterAuth({
database: prismaAdapter(prisma, {
provider: "postgresql", // "postgresql" | "mysql" | "sqlite"
})
});
```
**Generate Schema:**
```bash
npx @better-auth/cli generate --adapter prisma
```
**Apply to Prisma:**
```bash
# Add generated schema to schema.prisma
npx prisma migrate dev --name init
npx prisma generate
```
### Kysely
**Install:**
```bash
npm install kysely better-auth
```
**Setup:**
```ts
import { betterAuth } from "better-auth";
import { kyselyAdapter } from "better-auth/adapters/kysely";
import { Kysely, PostgresDialect } from "kysely";
import { Pool } from "pg";
const db = new Kysely({
dialect: new PostgresDialect({
pool: new Pool({
connectionString: process.env.DATABASE_URL
})
})
});
export const auth = betterAuth({
database: kyselyAdapter(db, {
provider: "pg"
})
});
```
**Auto-migrate with Kysely:**
```bash
npx @better-auth/cli migrate --adapter kysely
```
### MongoDB
**Install:**
```bash
npm install mongodb better-auth
```
**Setup:**
```ts
import { betterAuth } from "better-auth";
import { mongodbAdapter } from "better-auth/adapters/mongodb";
import { MongoClient } from "mongodb";
const client = new MongoClient(process.env.MONGODB_URI!);
await client.connect();
export const auth = betterAuth({
database: mongodbAdapter(client, {
databaseName: "myapp"
})
});
```
**Generate Collections:**
```bash
npx @better-auth/cli generate --adapter mongodb
```
## Core Database Schema
Better Auth requires these core tables/collections:
### User Table
```sql
CREATE TABLE user (
id TEXT PRIMARY KEY,
email TEXT UNIQUE NOT NULL,
emailVerified BOOLEAN DEFAULT FALSE,
name TEXT,
image TEXT,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
### Session Table
```sql
CREATE TABLE session (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
expiresAt TIMESTAMP NOT NULL,
ipAddress TEXT,
userAgent TEXT,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE
);
```
### Account Table
```sql
CREATE TABLE account (
id TEXT PRIMARY KEY,
userId TEXT NOT NULL,
accountId TEXT NOT NULL,
providerId TEXT NOT NULL,
accessToken TEXT,
refreshToken TEXT,
expiresAt TIMESTAMP,
scope TEXT,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (userId) REFERENCES user(id) ON DELETE CASCADE,
UNIQUE(providerId, accountId)
);
```
### Verification Table
```sql
CREATE TABLE verification (
id TEXT PRIMARY KEY,
identifier TEXT NOT NULL,
value TEXT NOT NULL,
expiresAt TIMESTAMP NOT NULL,
createdAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
updatedAt TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
```
## Schema Generation
### Using CLI
```bash
# Generate schema files
npx @better-auth/cli generate
# Specify adapter
npx @better-auth/cli generate --adapter drizzle
npx @better-auth/cli generate --adapter prisma
# Specify output
npx @better-auth/cli generate --output ./db/schema.ts
```
### Auto-migrate (Kysely only)
```bash
npx @better-auth/cli migrate
```
For other ORMs, apply generated schema manually.
## Custom Fields
Add custom fields to user table:
```ts
export const auth = betterAuth({
user: {
additionalFields: {
role: {
type: "string",
required: false,
defaultValue: "user"
},
phoneNumber: {
type: "string",
required: false
},
subscriptionTier: {
type: "string",
required: false
}
}
}
});
```
After adding fields:
```bash
npx @better-auth/cli generate
```
Update user with custom fields:
```ts
await authClient.updateUser({
role: "admin",
phoneNumber: "+1234567890"
});
```
## Plugin Schema Extensions
Plugins add their own tables/fields. Regenerate schema after adding plugins:
```bash
npx @better-auth/cli generate
```
### Two-Factor Plugin Tables
- `twoFactor`: Stores TOTP secrets, backup codes
### Passkey Plugin Tables
- `passkey`: Stores WebAuthn credentials
### Organization Plugin Tables
- `organization`: Organization data
- `member`: Organization members
- `invitation`: Pending invitations
## Migration Strategies
### Development
```bash
# Generate schema
npx @better-auth/cli generate
# Apply migrations (Kysely)
npx @better-auth/cli migrate
# Or manual (Prisma)
npx prisma migrate dev
# Or manual (Drizzle)
npx drizzle-kit push
```
### Production
```bash
# Review generated migration
npx @better-auth/cli generate
# Test in staging
# Apply to production with your ORM's migration tool
# Prisma
npx prisma migrate deploy
# Drizzle
npx drizzle-kit push
# Kysely
npx @better-auth/cli migrate
```
## Connection Pooling
### PostgreSQL
```ts
import { Pool } from "pg";
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
max: 20, // Max connections
idleTimeoutMillis: 30000,
connectionTimeoutMillis: 2000,
});
```
### MySQL
```ts
import { createPool } from "mysql2/promise";
const pool = createPool({
connectionString: process.env.DATABASE_URL,
waitForConnections: true,
connectionLimit: 10,
queueLimit: 0
});
```
## Database URLs
### PostgreSQL
```env
DATABASE_URL=postgresql://user:password@localhost:5432/dbname
# Or with connection params
DATABASE_URL=postgresql://user:password@localhost:5432/dbname?schema=public&connection_limit=10
```
### MySQL
```env
DATABASE_URL=mysql://user:password@localhost:3306/dbname
```
### SQLite
```env
DATABASE_URL=file:./dev.db
# Or in-memory
DATABASE_URL=:memory:
```
### MongoDB
```env
MONGODB_URI=mongodb://localhost:27017/dbname
# Or Atlas
MONGODB_URI=mongodb+srv://user:password@cluster.mongodb.net/dbname
```
## Performance Optimization
### Indexes
Better Auth CLI auto-generates essential indexes:
- `user.email` (unique)
- `session.userId`
- `account.userId`
- `account.providerId, accountId` (unique)
Add custom indexes for performance:
```sql
CREATE INDEX idx_session_expires ON session(expiresAt);
CREATE INDEX idx_user_created ON user(createdAt);
```
### Query Optimization
```ts
// Use connection pooling
// Enable query caching where applicable
// Monitor slow queries
export const auth = betterAuth({
advanced: {
defaultCookieAttributes: {
sameSite: "lax",
secure: true,
httpOnly: true
}
}
});
```
## Backup Strategies
### PostgreSQL
```bash
# Backup
pg_dump dbname > backup.sql
# Restore
psql dbname < backup.sql
```
### MySQL
```bash
# Backup
mysqldump -u root -p dbname > backup.sql
# Restore
mysql -u root -p dbname < backup.sql
```
### SQLite
```bash
# Copy file
cp dev.db dev.db.backup
# Or use backup command
sqlite3 dev.db ".backup backup.db"
```
### MongoDB
```bash
# Backup
mongodump --db=dbname --out=./backup
# Restore
mongorestore --db=dbname ./backup/dbname
```
## Best Practices
1. **Environment Variables**: Store credentials in env vars, never commit
2. **Connection Pooling**: Use pools for PostgreSQL/MySQL in production
3. **Migrations**: Use ORM migration tools, not raw SQL in production
4. **Indexes**: Add indexes for frequently queried fields
5. **Backups**: Automate daily backups in production
6. **SSL**: Use SSL/TLS for database connections in production
7. **Schema Sync**: Keep schema in sync across environments
8. **Testing**: Use separate database for tests (in-memory SQLite ideal)
9. **Monitoring**: Monitor query performance and connection pool usage
10. **Cleanup**: Periodically clean expired sessions/verifications
## Troubleshooting
### Connection Errors
```ts
// Add connection timeout
const pool = new Pool({
connectionString: process.env.DATABASE_URL,
connectionTimeoutMillis: 5000
});
```
### Schema Mismatch
```bash
# Regenerate schema
npx @better-auth/cli generate
# Apply migrations
# For Prisma: npx prisma migrate dev
# For Drizzle: npx drizzle-kit push
```
### Migration Failures
- Check database credentials
- Verify database server is running
- Check for schema conflicts
- Review migration SQL manually
### Performance Issues
- Add indexes on foreign keys
- Enable connection pooling
- Monitor slow queries
- Consider read replicas for heavy read workloads

View File

@@ -0,0 +1,416 @@
# Email/Password Authentication
Email/password is built-in auth method in Better Auth. No plugins required for basic functionality.
## Server Configuration
### Basic Setup
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
autoSignIn: true, // Auto sign-in after signup (default: true)
requireEmailVerification: false, // Require email verification before login
sendResetPasswordToken: async ({ user, url }) => {
// Send password reset email
await sendEmail(user.email, url);
}
}
});
```
### Custom Password Requirements
```ts
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
password: {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSpecialChars: true
}
}
});
```
## Client Usage
### Sign Up
```ts
import { authClient } from "@/lib/auth-client";
const { data, error } = await authClient.signUp.email({
email: "user@example.com",
password: "securePassword123",
name: "John Doe",
image: "https://example.com/avatar.jpg", // optional
callbackURL: "/dashboard" // optional
}, {
onSuccess: (ctx) => {
// ctx.data contains user and session
console.log("User created:", ctx.data.user);
},
onError: (ctx) => {
alert(ctx.error.message);
}
});
```
### Sign In
```ts
const { data, error } = await authClient.signIn.email({
email: "user@example.com",
password: "securePassword123",
callbackURL: "/dashboard",
rememberMe: true // default: true
}, {
onSuccess: () => {
// redirect or update UI
},
onError: (ctx) => {
console.error(ctx.error.message);
}
});
```
### Sign Out
```ts
await authClient.signOut({
fetchOptions: {
onSuccess: () => {
router.push("/login");
}
}
});
```
## Email Verification
### Server Setup
```ts
export const auth = betterAuth({
emailVerification: {
sendVerificationEmail: async ({ user, url, token }) => {
// Send verification email
await sendEmail({
to: user.email,
subject: "Verify your email",
html: `Click <a href="${url}">here</a> to verify your email.`
});
},
sendOnSignUp: true, // Send verification email on signup
autoSignInAfterVerification: true // Auto sign-in after verification
},
emailAndPassword: {
enabled: true,
requireEmailVerification: true // Require verification before login
}
});
```
### Client Usage
```ts
// Send verification email
await authClient.sendVerificationEmail({
email: "user@example.com",
callbackURL: "/verify-success"
});
// Verify email with token
await authClient.verifyEmail({
token: "verification-token-from-email"
});
```
## Password Reset Flow
### Server Setup
```ts
export const auth = betterAuth({
emailAndPassword: {
enabled: true,
sendResetPasswordToken: async ({ user, url, token }) => {
await sendEmail({
to: user.email,
subject: "Reset your password",
html: `Click <a href="${url}">here</a> to reset your password.`
});
}
}
});
```
### Client Flow
```ts
// Step 1: Request password reset
await authClient.forgetPassword({
email: "user@example.com",
redirectTo: "/reset-password"
});
// Step 2: Reset password with token
await authClient.resetPassword({
token: "reset-token-from-email",
password: "newSecurePassword123"
});
```
### Change Password (Authenticated)
```ts
await authClient.changePassword({
currentPassword: "oldPassword123",
newPassword: "newPassword456",
revokeOtherSessions: true // Optional: logout other sessions
});
```
## Username Authentication
Requires `username` plugin for username-based auth.
### Server Setup
```ts
import { betterAuth } from "better-auth";
import { username } from "better-auth/plugins";
export const auth = betterAuth({
plugins: [
username({
// Allow sign in with username or email
allowUsernameOrEmail: true
})
]
});
```
### Client Setup
```ts
import { createAuthClient } from "better-auth/client";
import { usernameClient } from "better-auth/client/plugins";
export const authClient = createAuthClient({
plugins: [usernameClient()]
});
```
### Client Usage
```ts
// Sign up with username
await authClient.signUp.username({
username: "johndoe",
password: "securePassword123",
email: "john@example.com", // optional
name: "John Doe"
});
// Sign in with username
await authClient.signIn.username({
username: "johndoe",
password: "securePassword123"
});
// Sign in with username or email (if allowUsernameOrEmail: true)
await authClient.signIn.username({
username: "johndoe", // or "john@example.com"
password: "securePassword123"
});
```
## Framework Setup
### Next.js (App Router)
```ts
// app/api/auth/[...all]/route.ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export const { POST, GET } = toNextJsHandler(auth);
```
### Next.js (Pages Router)
```ts
// pages/api/auth/[...all].ts
import { auth } from "@/lib/auth";
import { toNextJsHandler } from "better-auth/next-js";
export default toNextJsHandler(auth);
```
### Nuxt
```ts
// server/api/auth/[...all].ts
import { auth } from "~/utils/auth";
import { toWebRequest } from "better-auth/utils/web";
export default defineEventHandler((event) => {
return auth.handler(toWebRequest(event));
});
```
### SvelteKit
```ts
// hooks.server.ts
import { auth } from "$lib/auth";
import { svelteKitHandler } from "better-auth/svelte-kit";
export async function handle({ event, resolve }) {
return svelteKitHandler({ event, resolve, auth });
}
```
### Astro
```ts
// pages/api/auth/[...all].ts
import { auth } from "@/lib/auth";
export async function ALL({ request }: { request: Request }) {
return auth.handler(request);
}
```
### Hono
```ts
import { Hono } from "hono";
import { auth } from "./auth";
const app = new Hono();
app.on(["POST", "GET"], "/api/auth/*", (c) => {
return auth.handler(c.req.raw);
});
```
### Express
```ts
import express from "express";
import { toNodeHandler } from "better-auth/node";
import { auth } from "./auth";
const app = express();
app.all("/api/auth/*", toNodeHandler(auth));
```
## Protected Routes
### Next.js Middleware
```ts
// middleware.ts
import { auth } from "@/lib/auth";
import { NextRequest, NextResponse } from "next/server";
export async function middleware(request: NextRequest) {
const session = await auth.api.getSession({
headers: request.headers
});
if (!session) {
return NextResponse.redirect(new URL("/login", request.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/dashboard/:path*", "/profile/:path*"]
};
```
### SvelteKit Hooks
```ts
// hooks.server.ts
import { auth } from "$lib/auth";
import { redirect } from "@sveltejs/kit";
export async function handle({ event, resolve }) {
const session = await auth.api.getSession({
headers: event.request.headers
});
if (event.url.pathname.startsWith("/dashboard") && !session) {
throw redirect(303, "/login");
}
return resolve(event);
}
```
### Nuxt Middleware
```ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware(async (to) => {
const { data: session } = await useAuthSession();
if (!session.value && to.path.startsWith("/dashboard")) {
return navigateTo("/login");
}
});
```
## User Profile Management
### Get Current User
```ts
const { data: session } = await authClient.getSession();
console.log(session.user);
```
### Update User Profile
```ts
await authClient.updateUser({
name: "New Name",
image: "https://example.com/new-avatar.jpg",
// Custom fields if defined in schema
});
```
### Delete User Account
```ts
await authClient.deleteUser({
password: "currentPassword", // Required for security
callbackURL: "/" // Redirect after deletion
});
```
## Best Practices
1. **Password Security**: Enforce strong password requirements
2. **Email Verification**: Enable for production to prevent spam
3. **Rate Limiting**: Prevent brute force attacks (see advanced-features.md)
4. **HTTPS**: Always use HTTPS in production
5. **Error Messages**: Don't reveal if email exists during login
6. **Session Security**: Use secure, httpOnly cookies
7. **CSRF Protection**: Better Auth handles this automatically
8. **Password Reset**: Set short expiration for reset tokens
9. **Account Lockout**: Consider implementing after N failed attempts
10. **Audit Logs**: Track auth events for security monitoring

View File

@@ -0,0 +1,430 @@
# OAuth Providers
Better Auth provides built-in OAuth 2.0 support for social authentication. No plugins required.
## Supported Providers
GitHub, Google, Apple, Discord, Facebook, Microsoft, Twitter/X, Spotify, Twitch, LinkedIn, Dropbox, GitLab, and more.
## Basic OAuth Setup
### Server Configuration
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
// Optional: custom scopes
scope: ["user:email", "read:user"]
},
google: {
clientId: process.env.GOOGLE_CLIENT_ID!,
clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
scope: ["openid", "email", "profile"]
},
discord: {
clientId: process.env.DISCORD_CLIENT_ID!,
clientSecret: process.env.DISCORD_CLIENT_SECRET!,
}
}
});
```
### Client Usage
```ts
import { authClient } from "@/lib/auth-client";
// Basic sign in
await authClient.signIn.social({
provider: "github",
callbackURL: "/dashboard"
});
// With callbacks
await authClient.signIn.social({
provider: "google",
callbackURL: "/dashboard",
errorCallbackURL: "/error",
newUserCallbackURL: "/welcome", // For first-time users
});
```
## Provider Configuration
### GitHub OAuth
1. Create OAuth App at https://github.com/settings/developers
2. Set Authorization callback URL: `http://localhost:3000/api/auth/callback/github`
3. Add credentials to `.env`:
```env
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
```
### Google OAuth
1. Create project at https://console.cloud.google.com
2. Enable Google+ API
3. Create OAuth 2.0 credentials
4. Add authorized redirect URI: `http://localhost:3000/api/auth/callback/google`
5. Add credentials to `.env`:
```env
GOOGLE_CLIENT_ID=your_client_id.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=your_client_secret
```
### Discord OAuth
1. Create application at https://discord.com/developers/applications
2. Add OAuth2 redirect: `http://localhost:3000/api/auth/callback/discord`
3. Add credentials:
```env
DISCORD_CLIENT_ID=your_client_id
DISCORD_CLIENT_SECRET=your_client_secret
```
### Apple Sign In
```ts
export const auth = betterAuth({
socialProviders: {
apple: {
clientId: process.env.APPLE_CLIENT_ID!,
clientSecret: process.env.APPLE_CLIENT_SECRET!,
teamId: process.env.APPLE_TEAM_ID!,
keyId: process.env.APPLE_KEY_ID!,
privateKey: process.env.APPLE_PRIVATE_KEY!
}
}
});
```
### Microsoft/Azure AD
```ts
export const auth = betterAuth({
socialProviders: {
microsoft: {
clientId: process.env.MICROSOFT_CLIENT_ID!,
clientSecret: process.env.MICROSOFT_CLIENT_SECRET!,
tenantId: process.env.MICROSOFT_TENANT_ID, // Optional: for specific tenant
}
}
});
```
### Twitter/X OAuth
```ts
export const auth = betterAuth({
socialProviders: {
twitter: {
clientId: process.env.TWITTER_CLIENT_ID!,
clientSecret: process.env.TWITTER_CLIENT_SECRET!,
}
}
});
```
## Custom OAuth Provider
Add custom OAuth 2.0 provider:
```ts
import { betterAuth } from "better-auth";
export const auth = betterAuth({
socialProviders: {
customProvider: {
clientId: process.env.CUSTOM_CLIENT_ID!,
clientSecret: process.env.CUSTOM_CLIENT_SECRET!,
authorizationUrl: "https://provider.com/oauth/authorize",
tokenUrl: "https://provider.com/oauth/token",
userInfoUrl: "https://provider.com/oauth/userinfo",
scope: ["email", "profile"],
// Map provider user data to Better Auth user
mapProfile: (profile) => ({
id: profile.id,
email: profile.email,
name: profile.name,
image: profile.avatar_url
})
}
}
});
```
## Account Linking
Link multiple OAuth providers to same user account.
### Server Setup
```ts
export const auth = betterAuth({
account: {
accountLinking: {
enabled: true,
trustedProviders: ["google", "github"] // Auto-link these providers
}
}
});
```
### Client Usage
```ts
// Link new provider to existing account
await authClient.linkSocial({
provider: "google",
callbackURL: "/profile"
});
// List linked accounts
const { data: session } = await authClient.getSession();
const accounts = session.user.accounts;
// Unlink account
await authClient.unlinkAccount({
accountId: "account-id"
});
```
## Token Management
### Access OAuth Tokens
```ts
// Server-side
const session = await auth.api.getSession({
headers: request.headers
});
const accounts = await auth.api.listAccounts({
userId: session.user.id
});
// Get specific provider token
const githubAccount = accounts.find(a => a.providerId === "github");
const accessToken = githubAccount.accessToken;
const refreshToken = githubAccount.refreshToken;
```
### Refresh Tokens
```ts
// Manually refresh OAuth token
const newToken = await auth.api.refreshToken({
accountId: "account-id"
});
```
### Use Provider API
```ts
// Example: Use GitHub token to fetch repos
const githubAccount = accounts.find(a => a.providerId === "github");
const response = await fetch("https://api.github.com/user/repos", {
headers: {
Authorization: `Bearer ${githubAccount.accessToken}`
}
});
const repos = await response.json();
```
## Advanced OAuth Configuration
### Custom Scopes
```ts
export const auth = betterAuth({
socialProviders: {
github: {
clientId: process.env.GITHUB_CLIENT_ID!,
clientSecret: process.env.GITHUB_CLIENT_SECRET!,
scope: [
"user:email",
"read:user",
"repo", // Access repositories
"gist" // Access gists
]
}
}
});
```
### State Parameter
Better Auth automatically handles OAuth state parameter for CSRF protection.
```ts
// Custom state validation
export const auth = betterAuth({
advanced: {
generateState: async () => {
// Custom state generation
return crypto.randomUUID();
},
validateState: async (state: string) => {
// Custom state validation
return true;
}
}
});
```
### PKCE Support
Better Auth automatically uses PKCE (Proof Key for Code Exchange) for supported providers.
```ts
export const auth = betterAuth({
socialProviders: {
customProvider: {
pkce: true, // Enable PKCE
// ... other config
}
}
});
```
## Error Handling
### Client-Side
```ts
await authClient.signIn.social({
provider: "github",
errorCallbackURL: "/auth/error"
}, {
onError: (ctx) => {
console.error("OAuth error:", ctx.error);
// Handle specific errors
if (ctx.error.code === "OAUTH_ACCOUNT_ALREADY_LINKED") {
alert("This account is already linked to another user");
}
}
});
```
### Server-Side
```ts
export const auth = betterAuth({
callbacks: {
async onOAuthError({ error, provider }) {
console.error(`OAuth error with ${provider}:`, error);
// Log to monitoring service
await logError(error);
}
}
});
```
## Callback URLs
### Development
```
http://localhost:3000/api/auth/callback/{provider}
```
### Production
```
https://yourdomain.com/api/auth/callback/{provider}
```
**Important:** Add all callback URLs to OAuth provider settings.
## UI Components
### Sign In Button (React)
```tsx
import { authClient } from "@/lib/auth-client";
export function SocialSignIn() {
const handleOAuth = async (provider: string) => {
await authClient.signIn.social({
provider,
callbackURL: "/dashboard"
});
};
return (
<div className="space-y-2">
<button onClick={() => handleOAuth("github")}>
Sign in with GitHub
</button>
<button onClick={() => handleOAuth("google")}>
Sign in with Google
</button>
<button onClick={() => handleOAuth("discord")}>
Sign in with Discord
</button>
</div>
);
}
```
## Best Practices
1. **Callback URLs**: Add all environments (dev, staging, prod) to OAuth app
2. **Scopes**: Request minimum scopes needed
3. **Token Storage**: Better Auth stores tokens securely in database
4. **Token Refresh**: Implement automatic token refresh for long-lived sessions
5. **Account Linking**: Enable for better UX when user signs in with different providers
6. **Error Handling**: Provide clear error messages for OAuth failures
7. **Provider Icons**: Use official brand assets for OAuth buttons
8. **Mobile Deep Links**: Configure deep links for mobile OAuth flows
9. **Email Matching**: Consider auto-linking accounts with same email
10. **Privacy**: Inform users what data you access from OAuth providers
## Common Issues
### Redirect URI Mismatch
Ensure callback URL in OAuth app matches exactly:
```
http://localhost:3000/api/auth/callback/github
```
### Missing Scopes
Add required scopes for email access:
```ts
scope: ["user:email"] // GitHub
scope: ["email"] // Google
```
### HTTPS Required
Some providers (Apple, Microsoft) require HTTPS callbacks. Use ngrok for local development:
```bash
ngrok http 3000
```
### CORS Errors
Configure CORS if frontend/backend on different domains:
```ts
export const auth = betterAuth({
advanced: {
corsOptions: {
origin: ["https://yourdomain.com"],
credentials: true
}
}
});
```