authentication-setup
This skill should be used when setting up email OTP authentication with JWT sessions, password management, rate limiting, CSRF protection, and audit logging in a Next.js application. Use this skill when implementing a production-ready authentication system that matches the reference implementation pattern with Resend email, Prisma ORM, PostgreSQL, bcrypt password hashing, and jose JWT tokens.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
---
name: authentication-setup
description: This skill should be used when setting up email OTP authentication with JWT sessions, password management, rate limiting, CSRF protection, and audit logging in a Next.js application. Use this skill when implementing a production-ready authentication system that matches the reference implementation pattern with Resend email, Prisma ORM, PostgreSQL, bcrypt password hashing, and jose JWT tokens.
---
# Authentication Setup
## Overview
Set up a complete, production-ready authentication system for Next.js applications featuring:
- Email OTP authentication (primary method) via Resend
- Optional development password signin (behind strict feature flags)
- JWT session management with access and refresh tokens
- Email allowlist with configurable enforcement
- Signup toggle to control new user registration
- Rate limiting and optional hCaptcha integration
- CSRF protection via Origin/Referer validation
- Password management (set/change) with strength validation
- Comprehensive audit logging
- Session versioning for global invalidation
- Superadmin role with bypass capabilities
This implementation prioritizes security, user experience, and developer flexibility with extensive configuration options.
## When to Use This Skill
Use this skill when:
- Setting up authentication for a new Next.js application
- Migrating from a different auth system to this OTP-based approach
- Adding production-grade security features to existing authentication
- Implementing email-based verification flows with JWT sessions
- Questions about this authentication pattern's architecture or configuration
Do **not** use this skill when:
- Implementing OAuth/social login (Google, GitHub, etc.)
- Using third-party auth services (Auth0, Clerk, Supabase Auth)
- Building magic link authentication (different from OTP)
- Simple API key authentication
## Quick Start
For a rapid setup, follow these steps in order:
1. **Install dependencies**
```bash
npm install @prisma/client bcrypt jose resend zod @zxcvbn-ts/core @zxcvbn-ts/language-common @zxcvbn-ts/language-en
npm install -D prisma @types/bcrypt
```
See `references/dependencies.md` for complete details.
2. **Set up database schema**
- Copy the Prisma schema from `references/database_schema.md`
- Run `npx prisma generate` then `npx prisma migrate dev --name init_auth`
3. **Configure environment variables**
- Copy required variables from `references/environment_variables.md`
- Minimum: `DATABASE_URL`, `JWT_SECRET`, JWT cookie names, `APP_URL`, `ALLOWED_EMAILS`, Resend config
- Generate JWT secret: `openssl rand -base64 32`
4. **Implement core library files**
- Follow `references/implementation_guide.md` Step 3-5
- Create: `lib/db.ts`, `lib/env.ts`, `lib/jwt.ts`, `lib/jwt-edge.ts`
- Create: `lib/csrf.ts`, `lib/email.ts`, `lib/rate-limit.ts`
- Create: `lib/validators.ts`, `lib/auth.ts`, `lib/auth-helpers.ts`
5. **Add middleware**
- Create `middleware.ts` for Edge route protection
- See `references/implementation_guide.md` Step 6
6. **Create API routes**
- Implement auth endpoints in `app/api/auth/`
- Routes: `request-otp`, `verify-otp`, `dev-signin`, `signout`, `refresh`
- Profile routes: `profile/set-password`, `profile/change-password`
- See `references/implementation_guide.md` Step 7
7. **Build UI pages**
- Login page: `app/(public)/login/page.tsx`
- Protected layout: `app/(protected)/layout.tsx`
- Profile page: `app/(protected)/settings/profile/page.tsx`
- See `references/implementation_guide.md` Step 8
8. **Test and deploy**
- Follow testing checklist in `references/implementation_guide.md` Step 9
- Review production checklist before deploying (Step 10)
## Architecture Overview
### Authentication Flow
**Email OTP (Primary):**
1. User enters email at `/login`
2. System validates email against allowlist (if enabled) and checks signup toggle
3. Rate limiting enforced; hCaptcha required after threshold
4. OTP generated, hashed with bcrypt, stored in database
5. Email sent via Resend with 6-digit code (configurable)
6. User enters code within expiration window (default 10 minutes)
7. System verifies code, consumes token, creates/updates user
8. JWT tokens issued (access ~1h, refresh ~14d) in HTTP-only cookies
9. Audit log created for the event
**Dev Password Signin (Development Only):**
1. Only available when `NODE_ENV=development` and `ENABLE_DEV_PASSWORD_SIGNIN=true`
2. Returns 404 in production for security
3. User enters email + password
4. System verifies against bcrypt hash
5. JWT tokens issued on success
### Session Management
**Dual Token System:**
- **Access token** (~1 hour): Short-lived, verified by Edge middleware (signature only)
- **Refresh token** (~14 days): Long-lived, verified by Node.js endpoints with DB check
- Tokens rotate on refresh to maintain security
**Session Invalidation:**
- Increment `User.sessionVersion` to invalidate all sessions globally
- Automatically incremented on password set/change
- Token verification compares `tokenVersion` claim to `User.sessionVersion`
**Edge Middleware:**
- Verifies JWT signature only (no database access)
- Redirects unauthenticated users to `/login?next=...`
- Protected routes: `/dashboard`, `/settings`, etc.
- Public routes: `/`, `/login`, `/api/auth/*`
### Security Features
**Email Allowlist:**
- Configurable via `AUTH_ALLOWLIST_ENABLED` (default: true)
- Comma-separated list in `ALLOWED_EMAILS` (normalized lowercase)
- Exact match required (no wildcards)
- Superadmins bypass this check
- Generic error messages to prevent email enumeration
**Signup Control:**
- Configurable via `AUTH_SIGNUP_ENABLED` (default: true)
- When disabled, only existing users can sign in
- Explicit error messages for better UX
- Superadmins bypass this restriction
**Rate Limiting:**
- Per email: 3 requests per 15 minutes, 10 per 24 hours
- Per IP: 5 requests per 15 minutes
- hCaptcha required after ≥2 requests in 15 minutes (when configured)
- Stored in `OtpRequest` table for tracking
**CSRF Protection:**
- All mutation endpoints validate Origin/Referer headers
- Allowlist includes `APP_URL` and optional `ALLOWED_ORIGINS`
- Development: auto-includes localhost variants
**Password Security:**
- Bcrypt hashing with configurable rounds (default: 12)
- zxcvbn strength validation (score ≥3 required)
- Min 8 characters
- Dev bypass: `SKIP_PASSWORD_VALIDATION=true` (set-password only)
**OTP Security:**
- Bcrypt-hashed storage (never plaintext)
- Single active token per email
- Configurable length (4-8 digits, default: 6)
- Configurable expiration (default: 10 minutes)
- Token consumed after 5 failed attempts
- New request invalidates previous unconsumed tokens
### Database Models
Four core models (see `references/database_schema.md` for complete schemas):
1. **User**: Authentication data, role, session version
2. **OtpToken**: Hashed OTP codes with expiration and attempt tracking
3. **OtpRequest**: Rate limiting tracking per email and IP
4. **AuditLog**: Comprehensive event logging for security monitoring
### Superadmin Role
Special user role with elevated privileges:
**Capabilities:**
- Bypass email allowlist checks
- Bypass signup toggle restrictions
- Create unlimited organizations (bypasses `ORG_CREATION_LIMIT`)
- Global access to all organizations without explicit membership
**Security:**
- Only grant to trusted system administrators
- Create via seed script: `scripts/seed-superadmin.ts`
- Set `SEED_EMAIL` environment variable and run: `npx tsx scripts/seed-superadmin.ts`
**Important:** Superadmins are security-critical accounts. Use sparingly and audit regularly.
## Configuration Reference
### Environment Variables
All configuration is done via environment variables. See `references/environment_variables.md` for complete documentation.
**Critical variables:**
- `DATABASE_URL`: PostgreSQL connection string
- `JWT_SECRET`: ≥32 characters, use `openssl rand -base64 32`
- `JWT_ACCESS_COOKIE_NAME`, `JWT_REFRESH_COOKIE_NAME`: Cookie names for tokens
- `RESEND_API_KEY`, `RESEND_FROM_EMAIL`: Email service credentials
- `APP_URL`: Application base URL for CSRF validation
- `ALLOWED_EMAILS`: Comma-separated email allowlist
**Feature toggles:**
- `AUTH_ALLOWLIST_ENABLED`: Enable/disable email allowlist (default: true)
- `AUTH_SIGNUP_ENABLED`: Enable/disable new user signups (default: true)
- `ENABLE_DEV_PASSWORD_SIGNIN`: Enable dev password signin (dev only)
- `SKIP_PASSWORD_VALIDATION`: Bypass strength check in dev (set-password only)
- `ORG_CREATION_ENABLED`: Allow organization creation (default: false)
- `HCAPTCHA_ENABLED`: Enable CAPTCHA after rate limit threshold
**Tunable parameters:**
- `OTP_EXP_MINUTES`: OTP expiration in minutes (default: 10)
- `OTP_LENGTH`: OTP code length, 4-8 digits (default: 6)
- `BCRYPT_ROUNDS`: Bcrypt hashing rounds, 10-15 (default: 12)
- `ORG_CREATION_LIMIT`: Max orgs per user (default: 1)
### File Structure
Complete file structure with all required files:
```
app/
├── (public)/login/page.tsx # Login with OTP + dev password
├── (protected)/
│ ├── layout.tsx # Auth guard wrapper
│ ├── dashboard/page.tsx # Protected page example
│ └── settings/profile/page.tsx # Password management
└── api/auth/
├── request-otp/route.ts # Send OTP email
├── verify-otp/route.ts # Verify OTP + create session
├── dev-signin/route.ts # Dev password signin
├── signout/route.ts # Clear session
├── refresh/route.ts # Rotate tokens
└── profile/
├── set-password/route.ts # Set initial password
└── change-password/route.ts # Change existing password
lib/
├── db.ts # Prisma client singleton
├── env.ts # Environment validation
├── jwt.ts # JWT for Node.js
├── jwt-edge.ts # JWT for Edge
├── csrf.ts # CSRF validation
├── rate-limit.ts # Rate limiting
├── email.ts # Email sending
├── validators.ts # Zod schemas + password validation
├── auth.ts # Core auth logic
└── auth-helpers.ts # Session helpers
middleware.ts # Edge auth guard
prisma/schema.prisma # Database models
scripts/seed-superadmin.ts # Superadmin creation
```
See `references/implementation_guide.md` for detailed code examples for each file.
## Implementation Workflow
### Step-by-Step Guide
Follow this workflow to implement the complete authentication system:
**Phase 1: Foundation (30-60 minutes)**
1. Install all dependencies (`references/dependencies.md`)
2. Set up Prisma schema and run migrations (`references/database_schema.md`)
3. Configure environment variables (`references/environment_variables.md`)
4. Create core library files: db, env, jwt, csrf (`references/implementation_guide.md` Steps 3-4)
**Phase 2: Authentication Logic (60-90 minutes)**
1. Implement email sending (`lib/email.ts`)
2. Build rate limiting logic (`lib/rate-limit.ts`)
3. Create Zod validators and password strength checking (`lib/validators.ts`)
4. Implement auth functions: OTP, passwords, audit (`lib/auth.ts`)
5. Create session management helpers (`lib/auth-helpers.ts`)
**Phase 3: API Routes (60-90 minutes)**
1. Implement `POST /api/auth/request-otp` (send OTP)
2. Implement `POST /api/auth/verify-otp` (verify + create session)
3. Implement `POST /api/auth/dev-signin` (dev password login)
4. Implement `POST /api/auth/signout` (clear session)
5. Implement `GET /api/auth/refresh` (rotate tokens)
6. Implement `POST /api/auth/profile/set-password`
7. Implement `POST /api/auth/profile/change-password`
**Phase 4: Middleware & Protection (15-30 minutes)**
1. Create Edge middleware (`middleware.ts`)
2. Set up protected layout (`app/(protected)/layout.tsx`)
3. Verify redirects work correctly
**Phase 5: UI Pages (90-120 minutes)**
1. Build login page with OTP flow (`app/(public)/login/page.tsx`)
2. Add dev password tab (conditional rendering)
3. Implement profile page with password management (`app/(protected)/settings/profile/page.tsx`)
4. Add shadcn/ui components as needed (Button, Input, Form, Tabs, Card, etc.)
5. Integrate toast notifications with Sonner
**Phase 6: Testing & Refinement (60-90 minutes)**
1. Test complete OTP flow end-to-end
2. Verify email allowlist enforcement
3. Test rate limiting and CAPTCHA (if enabled)
4. Verify CSRF protection
5. Test password set/change with session rotation
6. Verify middleware protection and redirects
7. Check audit logs are created
8. Test dev password signin (development only)
**Total estimated time:** 5-8 hours for complete implementation
### Common Implementation Patterns
**Protected API Route Pattern:**
```typescript
export const runtime = "nodejs" // Required for DB access
export async function POST(req: Request) {
// 1. CSRF validation
if (!isRequestOriginValid(req)) {
return Response.json({ error: 'Invalid origin' }, { status: 403 })
}
// 2. Get current user
const user = await getCurrentUser()
if (!user) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
// 3. Parse and validate request
const body = await req.json()
const data = yourSchema.parse(body)
// 4. Perform operation
// ...
// 5. Audit log
await auditLog({
action: 'action_name',
userId: user.id,
email: user.email,
ip: req.headers.get('x-forwarded-for') || 'unknown',
})
// 6. Return response
return Response.json({ ok: true })
}
```
**Protected Server Component Pattern:**
```typescript
import { getCurrentUser } from '@/lib/auth-helpers'
import { redirect } from 'next/navigation'
export default async function ProtectedPage() {
const user = await getCurrentUser()
if (!user) {
redirect('/login')
}
return <div>Protected content for {user.email}</div>
}
```
**Client Form with Validation:**
```typescript
'use client'
import { useForm } from 'react-hook-form'
import { zodResolver } from '@hookform/resolvers/zod'
import { toast } from 'sonner'
export function MyForm() {
const form = useForm({
resolver: zodResolver(mySchema),
})
async function onSubmit(data) {
try {
const res = await fetch('/api/auth/endpoint', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data),
})
const result = await res.json()
if (!res.ok) {
toast.error(result.error || 'Operation failed')
return
}
toast.success('Success!')
} catch (error) {
toast.error('Network error. Please try again.')
}
}
return <form onSubmit={form.handleSubmit(onSubmit)}>...</form>
}
```
## Audit Logging
All authentication events are logged to the `AuditLog` table for security monitoring and compliance.
**Logged Events:**
- `otp_request`: OTP email sent
- `otp_request_blocked`: OTP request blocked (rate limit, allowlist, signup disabled)
- `otp_verify_success`: User successfully verified OTP
- `otp_verify_failure`: OTP verification failed (reasons in metadata)
- `dev_signin_success`: Dev password signin succeeded
- `dev_signin_failure`: Dev password signin failed (reasons in metadata)
- `password_set`: User set initial password
- `password_change_success`: User changed password
- `password_change_failure`: Password change failed (wrong current password)
- `signout`: User signed out
**Metadata Examples:**
- Failed verification: `{ reason: 'invalid_code', attempts: 3 }`
- Blocked request: `{ reason: 'rate_limit_exceeded', email_15m_count: 4 }`
- Signup disabled: `{ reason: 'signup_disabled_no_account' }`
Query audit logs for security monitoring:
```typescript
const recentFailures = await db.auditLog.findMany({
where: {
action: 'otp_verify_failure',
createdAt: { gte: new Date(Date.now() - 24 * 60 * 60 * 1000) },
},
orderBy: { createdAt: 'desc' },
})
```
## Production Deployment
Before deploying to production, complete this checklist:
**Environment Configuration:**
- [ ] Set `NODE_ENV=production`
- [ ] Generate strong `JWT_SECRET` (≥32 chars, never reuse from dev)
- [ ] Configure production `APP_URL` (actual domain)
- [ ] Set up Resend with verified domain
- [ ] Configure `ALLOWED_EMAILS` appropriately or set `AUTH_ALLOWLIST_ENABLED=false`
- [ ] Set `ENABLE_DEV_PASSWORD_SIGNIN=false` or remove entirely
- [ ] Consider enabling `HCAPTCHA_*` for bot protection
- [ ] Review `AUTH_SIGNUP_ENABLED` setting based on your use case
**Database & Infrastructure:**
- [ ] Set up production PostgreSQL database
- [ ] Run migrations: `npx prisma migrate deploy`
- [ ] Enable database backups (automated, tested restore process)
- [ ] Set up monitoring for database performance
- [ ] Consider connection pooling (PgBouncer, Prisma Data Proxy)
**Security Hardening:**
- [ ] Review all environment variables for sensitive data
- [ ] Ensure cookies use `secure: true` in production
- [ ] Set up rate limiting at infrastructure level (Cloudflare, etc.)
- [ ] Configure CSP headers for additional XSS protection
- [ ] Review and test CSRF protection
- [ ] Set up security monitoring and alerts
**Testing:**
- [ ] Test complete OTP flow end-to-end in production-like environment
- [ ] Verify email deliverability (check spam folders)
- [ ] Test rate limiting behavior
- [ ] Verify CSRF protection blocks invalid origins
- [ ] Test session expiration and refresh
- [ ] Verify middleware protects all routes correctly
- [ ] Test password set/change flows
**Monitoring & Maintenance:**
- [ ] Set up application logging (errors, warnings)
- [ ] Monitor audit logs for suspicious activity
- [ ] Set up alerts for failed auth attempts
- [ ] Plan for `OtpRequest` cleanup (cron job to delete old records)
- [ ] Document incident response procedures
- [ ] Schedule regular security reviews
**Email Deliverability:**
- [ ] Verify Resend domain and DNS records
- [ ] Test emails to major providers (Gmail, Outlook, etc.)
- [ ] Set up SPF, DKIM, and DMARC records
- [ ] Monitor bounce rates and spam reports
- [ ] Have fallback contact method if emails fail
## Troubleshooting
### Common Issues
**OTP emails not sending:**
- Verify `RESEND_API_KEY` is valid and not expired
- Check that sending domain is verified in Resend dashboard
- Review application logs for Resend API errors
- Test with a simple curl request to Resend API
- Check spam folders on recipient email
- Verify `RESEND_FROM_EMAIL` uses verified domain
**JWT verification failures:**
- Ensure `JWT_SECRET` is identical across all instances/reploys
- Check cookie settings (httpOnly, secure, sameSite, path)
- Verify token hasn't expired (check exp claim)
- Ensure `sessionVersion` matches between token and database
- Check browser developer tools for cookie presence
**Rate limiting too aggressive:**
- Adjust limits via environment variables (see `environment_variables.md`)
- Consider clearing old `OtpRequest` records
- Review IP detection logic (check `x-forwarded-for` header)
- Test from different IPs to isolate email vs IP limits
**Middleware redirect loops:**
- Ensure `/login` is in `PUBLIC_PATHS` constant
- Verify all `/api/auth/*` routes are public
- Check Edge runtime compatibility of imported modules
- Review middleware matcher configuration
- Test with browser developer tools network tab
**CSRF validation blocking legitimate requests:**
- Verify `APP_URL` matches actual application domain
- Check Origin/Referer headers in browser developer tools
- Add additional origins to `ALLOWED_ORIGINS` if needed
- Ensure forms submit to same origin
- Review CORS configuration if using separate frontend
**Password strength validation too strict:**
- Review zxcvbn feed