Password Authentication (Email + Password) Setup Guide
Password Authentication is a traditional email + password login method where users create accounts with a password and verify their email via a secure link. After verification, users can log in with email and password, and change or reset their password as needed.
Password authentication provides two main workflows:
- Registration — New user creates account with email, password, and name; verifies email via link
- Login — Existing verified user authenticates with email and password (with optional 2FA)
Additional flows: Password Reset (forgot password via email), Password Change (authenticated password update), Email Verification (admin resend).
Registration Flow
Section titled “Registration Flow”Step 1: User Submits Registration Form
Section titled “Step 1: User Submits Registration Form”Endpoint: POST /auth/register-password
Request:
{ "email": "user@example.com", "name": "Alice Johnson", "password": "SecureP@ss123!", "cf-turnstile-response": "<turnstile-token>"}Security Checks:
- ✅ Turnstile bot protection (required)
- ✅ IP-based registration rate limit (5 registrations/hour per IP)
- ✅ Email velocity tracking (3 attempts/hour per email, 5+ IPs = harassment detected)
- ✅ Password policy validation (min 8 chars, complexity requirements)
- ✅ Password breach check (Have I Been Pwned via k-anonymity)
Step 2: Account Created with Pending Verification
Section titled “Step 2: Account Created with Pending Verification”If all checks pass:
- New user record created in backend with
authentication_status: "Email Verification Pending" authentication_type: ["Password"]set (user can add Magic Login later)_2fa_status: "Disabled"(user can enable 2FA after email verification)- Password hashed with per-user salt (stored as
password_hashandpassword_salt) - Account is locked until email verification completes
Response:
{ "success": true, "recordid": "123456789", "message": "Account created. Please check your email to verify your account."}Step 3: Verification Email Sent
Section titled “Step 3: Verification Email Sent”- Verification token (32-byte, base64-encoded) generated
- Token stored in Cloudflare KV with 24-hour expiration
- Email sent via
emailVerificationWebhook(tenant-configured automation) - Email contains verification link:
https://portal.example.com/email-verified/{recordId}/{token}
Webhook Payload:
{ "event_type": "verify_email", "recordid": "123456789", "email": "user@example.com", "name": "Alice Johnson", "verificationLink": "https://your.domain.com/email-verified/123456789/abc123..."}Step 4: User Verifies Email
Section titled “Step 4: User Verifies Email”Endpoint: POST /auth/verify-email
Request:
{ "recordId": "123456789", "token": "abc123..."}Validation:
- Token must match Cloudflare KV stored token (one-time use)
- Token must not be expired (24-hour window)
On Success:
authentication_statusset to"Unlocked"- Token deleted from Cloudflare KV (can’t be reused)
- User account is now active and can log in
Login Flow
Section titled “Login Flow”Step 1: User Submits Login Form
Section titled “Step 1: User Submits Login Form”Endpoint: POST /auth/login
Request:
{ "email": "user@example.com", "password": "SecureP@ss123!", "cf-turnstile-response": "<turnstile-token>"}Security Checks:
- ✅ Turnstile bot protection
- ✅ Login velocity tracking (detects distributed attacks: 10+ IPs in 15 min)
- ✅ Progressive lockout (exponential penalties: 1 hour → 4 hours → 24 hours → 7 days)
- ✅ Account status validation:
- Account must not be
Locked(manually locked by admin) - Email must be verified (
authentication_status != "Email Verification Pending") - Password change not required (
authentication_status != "Password Change Pending")
- Account must not be
Step 2: Password Verification
Section titled “Step 2: Password Verification”If Turnstile and velocity checks pass:
- Password hashed with stored salt
- Hash compared to stored
password_hash - If mismatch: increment failed login count and apply progressive lockout
Failed Login Response (429):
{ "authorized": false, "message": "Too many failed attempts. Please try again later."}Step 3a: Password Correct, No 2FA Required
Section titled “Step 3a: Password Correct, No 2FA Required”If password matches and 2FA disabled:
- Session created with user’s recordId
- Session cookie set:
session_id={sessionId}; HttpOnly; Secure; SameSite=Lax - Session TTL determined by
sessionTier(15 mins to 30 days) - Geo-location tracked (for anomaly detection on next login)
Response (200 OK):
{ "authorized": true, "recordid": "123456789", "redirectUrl": "/dashboard"}Set-Cookie Header:
session_id=sess_abc123...; Path=/; HttpOnly; Secure; SameSite=Lax; Max-Age=3600Step 3b: Password Correct, 2FA Required
Section titled “Step 3b: Password Correct, 2FA Required”If password matches but 2FA is enabled and no code provided:
Response (200 OK):
{ "authorized": false, "two_factor_required": true, "message": "Please enter your 2FA code."}Frontend prompts for 2FA code and re-calls endpoint with twoFACode:
{ "email": "user@example.com", "password": "SecureP@ss123!", "twoFACode": "123456", "cf-turnstile-response": "<turnstile-token>"}2FA Validation:
- ✅ TOTP code verified (time-based one-time password)
- ✅ OR recovery code verified (one-time use recovery codes)
- ✅ Max 3 failed 2FA attempts before temporary lockout (15 minutes)
2FA Success: Session created and returned as above.
Password Reset Flow (Forgot Password)
Section titled “Password Reset Flow (Forgot Password)”Step 1: User Requests Password Reset
Section titled “Step 1: User Requests Password Reset”Endpoint: POST /auth/forgot-password
Request:
{ "email": "user@example.com", "cf-turnstile-response": "<turnstile-token>"}Security Checks:
- ✅ Turnstile bot protection (required)
- ✅ IP-based rate limit (10 requests/hour per IP)
- ✅ Email velocity tracking (5+ IPs in 1 hour = harassment)
Step 2: Conditions for Sending Reset Email
Section titled “Step 2: Conditions for Sending Reset Email”| Condition | Result |
|---|---|
| Account not found | Generic success (email enumeration protection) |
| Account locked | Generic success (don’t reveal locked status) |
| All checks pass | Password reset email sent |
Why generic responses? Prevents attackers from discovering which emails are registered.
Step 3: Reset Email Sent
Section titled “Step 3: Reset Email Sent”- Reset token (32-byte, base64-encoded) generated
- Token stored in Cloudflare KV with 24-hour expiration
- Email sent via
passwordResetWebhook(tenant-configured automation) - Email contains reset link:
https://portal.example.com/reset-password/{recordId}/{token} - All existing sessions for this user are invalidated (forced re-login after password change)
Webhook Payload:
{ "event_type": "password_reset", "recordid": "123456789", "email": "user@example.com", "name": "Alice Johnson", "resetLink": "https://your.domain.com/reset-password/123456789/xyz789..."}Step 4: User Resets Password
Section titled “Step 4: User Resets Password”Endpoint: POST /auth/reset-password
Request:
{ "recordId": "123456789", "token": "xyz789...", "newPassword": "NewSecureP@ss456!"}Validation:
- ✅ Reset token must match Cloudflare KV (one-time use)
- ✅ Token must not be expired (24-hour window)
- ✅ New password must pass policy (min 8 chars, complexity, breach check)
On Success:
- Token deleted from Cloudflare KV (can’t be reused)
- Password hashed with new salt and stored
- All sessions invalidated (user must log in again)
authentication_statusreset to"Unlocked"(if was “Password Change Pending”, now clear)
Response (200 OK):
{ "success": true, "message": "Password updated successfully. Please log in with your new password."}Password Change Flow (Authenticated User)
Section titled “Password Change Flow (Authenticated User)”Step 1: Logged-In User Changes Password
Section titled “Step 1: Logged-In User Changes Password”Endpoint: POST /auth/change-password
Request:
Headers:
Cookie: session_id=sess_abc123...X-Tenant-Domain: portal.example.comBody:
{ "currentPassword": "SecureP@ss123!", "newPassword": "NewSecureP@ss456!"}Security Checks:
- ✅ Session validation (user must be logged in)
- ✅ Current password verification (prevent unauthorized changes)
- ✅ Password policy validation (same as registration)
- ✅ Password breach check (Have I Been Pwned)
Step 2: Change Successful
Section titled “Step 2: Change Successful”- Old password hashes invalidated
- New password hashed with new salt
- All other sessions for this user invalidated (forced re-login on other devices)
- Current session remains active (no immediate logout)
Response (200 OK):
{ "success": true, "message": "Password changed successfully."}Admin: Resend Email Verification
Section titled “Admin: Resend Email Verification”Endpoint: POST /auth/admin/resend-verification-email
For cases where user’s verification email was lost or expired. See Resend Verification Email guide for details.
Token Security
Section titled “Token Security”One-Time Use Tokens
Section titled “One-Time Use Tokens”- Tokens are deleted immediately after use (Cloudflare KV.delete)
- Cannot be reused even within the expiration window
- If user clicks link twice, second click fails with “Invalid or expired token”
Token Format
Section titled “Token Format”- Size: 32 random bytes (256 bits of entropy)
- Encoding: Base64 URL-safe (+ replaced with -, / replaced with _, = removed)
- Example:
abc123-_XYZ789(no +/= characters)
Token Storage
Section titled “Token Storage”- Cloudflare KV Key Format:
- Email verification:
email_verify:{hostname}:{recordId} - Password reset:
password_reset:{hostname}:{recordId}
- Email verification:
- TTL:
- Email verification: 24 hours
- Password reset: 24 hours
- No plaintext in logs — only token existence is logged, never the actual token
Password Storage & Security
Section titled “Password Storage & Security”Hashing Algorithm
Section titled “Hashing Algorithm”- Per-user salt: 32 random bytes generated at registration
- Hash function: PBKDF2 with SHA-256
- Stored fields:
password_hashandpassword_saltin user record
Password Policy
Section titled “Password Policy”Enforced at registration and reset:
- Minimum 8 characters
- At least one uppercase letter
- At least one lowercase letter
- At least one number or special character
- Not in Have I Been Pwned database (checked via k-anonymity API)
Rate Limiting & Abuse Prevention
Section titled “Rate Limiting & Abuse Prevention”Registration Velocity Limits
Section titled “Registration Velocity Limits”| Limit | Scope | Window | Block Action |
|---|---|---|---|
| 5 registrations | Single IP | 1 hour | 429 Too Many Requests |
| 3 attempts | Single email | 1 hour | 429 Too Many Requests |
| 5+ IPs | Single email | 1 hour | Log warning (allowed but flagged) |
Login Velocity & Progressive Lockout
Section titled “Login Velocity & Progressive Lockout”| Threshold | Action |
|---|---|
| 5 IPs in 15 min | Logged as suspicious |
| 10 IPs in 15 min | 30-minute lockout |
| 3 failed login attempts | 1-hour lockout (exponential: 1h → 4h → 24h → 7 days) |
Password Reset Request Limits
Section titled “Password Reset Request Limits”| Limit | Scope | Window | Block Action |
|---|---|---|---|
| 10 requests | Single IP | 1 hour | 429 Too Many Requests |
| 5+ IPs | Single email | 1 hour | 429 Too Many Requests |
Session Creation
Section titled “Session Creation”After successful login, a session is created on Mindful Auth Cloudflare KV with:
Session Data (stored in KV):
{ "recordId": "123456789", "hostname": "portal.example.com", "iat": 1705779445, "exp": 1705783045, "is2FAEnabled": false, "require2FASetup": false, "passwordChangePending": false, "loginContext": null, "attemptedPath": null}Session Cookie (sent to client):
- Name:
session_id - Value: Cryptographically random sessionId
- Flags:
HttpOnly— JavaScript cannot access (prevents XSS token theft)Secure— Only sent over HTTPS (prevents man-in-the-middle)SameSite=Lax— Prevents CSRF attacks on form submissionsMax-Age— Matches configured session TTL (15 mins to 30 days)Path=/— Valid for all paths on domain
Comparison: Password vs. Magic Link
Section titled “Comparison: Password vs. Magic Link”| Feature | Password | Magic Link |
|---|---|---|
| Credentials required | Email + password | Email only |
| Verification | Email link + password | Email link |
| Reset flow | Password reset via email | Password reset via email (or add password) |
| Phishing risk | Medium (password reuse) | Low (token expires in 15 min) |
| Usability | Medium (remember password) | High (click link) |
| 2FA capable | Yes (optional) | Yes (optional) |
| Session TTL | Configurable (15 min - 30 days) | Configurable (15 min - 30 days) |
| Hybrid support | Yes (can add magic link) | Yes (can add password) |
Audit Events
Section titled “Audit Events”Password authentication generates comprehensive audit logs. See Audit Logs Guide for details.
Adding Password Authentication to Your Tenant
Section titled “Adding Password Authentication to Your Tenant”1. Configure Webhook URLs
Section titled “1. Configure Webhook URLs”During tenant onboarding, provide:
{ "emailVerificationWebhook": "https://your-automation.com/verify-email", "passwordResetWebhook": "https://your-automation.com/reset-password"}These are webhook URLs pointing to your email automation service (Tape automation, Make, Zapier, n8n, etc.).
2. Create Email Templates
Section titled “2. Create Email Templates”Your automation should:
- Accept
recordid,email,name,verificationLink(registration flow) - Accept
recordid,email,name,resetLink(password reset) - Send branded emails with secure links
- Never log or display tokens in plaintext
3. Frontend Setup
Section titled “3. Frontend Setup”- Create registration form with email, name, password, and Turnstile widget
- Create login form with email, password, and Turnstile widget
- Create
/email-verified/{recordId}/{token}page to verify email - Create
/reset-password/{recordId}/{token}page to reset password - Create authenticated password change page for logged-in users
- Handle
two_factor_required: trueresponse by prompting for 2FA code
Common Issues & Solutions
Section titled “Common Issues & Solutions”| Issue | Cause | Solution |
|---|---|---|
| User gets “Invalid or expired token” | Token expired after 24h or already used | Request new verification/reset email |
| ”You’ve registered too many times from this IP” | Rate limit triggered | Wait 1 hour or use different IP |
| ”Password doesn’t meet policy requirements” | Weak password or breach check failed | Use stronger password; check Have I Been Pwned |
| ”Too many failed login attempts” | Progressive lockout triggered | Wait 1 hour (or longer if repeating) |
| “Your account has been locked” | Admin locked account | Contact support to unlock |
| Email never arrives | Webhook not configured or automation failed | Check webhook URL in tenant config |
| User can’t log in after password reset | All sessions invalidated, browser cache issue | Clear cookies and try again |
| ”Email verification pending” error on login | User hasn’t clicked verification link yet | Send verification email and click link |