Authentication
Sunday provides a complete authentication system with email verification and password reset.
Features
- Email/Password Registration - Secure signup with password hashing
- Email Verification - Confirm user email addresses
- JWT Tokens - Secure session management
- Password Reset - Email-based password recovery
- Rate Limiting - Protection against brute force attacks
- CAPTCHA - Spam prevention on auth forms
Sign Up Flow
Signup Request
POST /api/auth/signup
Content-Type: application/json
{
"name": "John Doe",
"email": "john@example.com",
"password": "securePassword123",
"captchaToken": "cap_..." // Optional CAPTCHA token
}Response
{
"success": true,
"message": "Account created. Please check your email to verify."
}Sign In Flow
Signin Request
POST /api/auth/signin
Content-Type: application/json
{
"email": "john@example.com",
"password": "securePassword123"
}Response
{
"success": true,
"token": "eyJhbGciOiJIUzI1NiIs...",
"user": {
"id": "507f1f77bcf86cd799439011",
"name": "John Doe",
"email": "john@example.com",
"avatar": "/default-avatar.png"
}
}Password Reset Flow
Request Reset
POST /api/auth/forgot-password
Content-Type: application/json
{
"email": "john@example.com"
}Reset Password
POST /api/auth/reset-password
Content-Type: application/json
{
"token": "reset-token-from-email",
"password": "newSecurePassword456"
}Security Features
Password Hashing
Passwords are hashed using bcrypt with a cost factor of 12:
import { hash, compare } from "bcryptjs"
// Hash password on signup
const hashedPassword = await hash(password, 12)
// Verify password on signin
const isValid = await compare(password, hashedPassword)JWT Tokens
Tokens are signed with HS256 and expire after 7 days:
import { sign, verify } from "jsonwebtoken"
// Generate token
const token = sign(
{ userId, email },
process.env.JWT_SECRET,
{ expiresIn: "7d" }
)
// Verify token
const decoded = verify(token, process.env.JWT_SECRET)Rate Limiting
Authentication endpoints are rate limited:
| Endpoint | Limit | Window |
|---|---|---|
/api/auth/signin | 5 requests | 1 minute |
/api/auth/signup | 5 requests | 1 minute |
/api/auth/forgot-password | 5 requests | 1 minute |
Email Verification Tokens
Verification tokens:
- Generated using
crypto.randomBytes(32) - Hashed with SHA-256 before storage
- Expire after 24 hours
import crypto from "crypto"
// Generate token
const token = crypto.randomBytes(32).toString("hex")
// Hash for storage
const hashedToken = crypto
.createHash("sha256")
.update(token)
.digest("hex")Email Templates
Verification and password reset emails use Apple-inspired designs:
- Clean, minimal layout
- Clear call-to-action button
- Mobile-responsive
- Both HTML and plain text versions
Client-Side Usage
Store Authentication State
import { useAppStore } from '@/lib/store'
function AuthProvider({ children }) {
const { currentUser, setCurrentUser } = useAppStore()
useEffect(() => {
// Check for stored token
const token = localStorage.getItem('authToken')
if (token) {
// Verify and fetch user
fetch('/api/auth/me', {
headers: { Authorization: `Bearer ${token}` }
})
.then(res => res.json())
.then(user => setCurrentUser(user))
}
}, [])
return children
}Protected Routes
function ProtectedPage() {
const currentUser = useAppStore((s) => s.currentUser)
const router = useRouter()
useEffect(() => {
if (!currentUser) {
router.push('/signin')
}
}, [currentUser])
if (!currentUser) return null
return <div>Protected content</div>
}