Building Authentication from Scratch vs Using a Boilerplate (2026)
TL;DR
Authentication is one of the highest-leverage things a boilerplate handles. Getting auth right from scratch takes 5-10 days and requires deep security knowledge. Getting it wrong creates account takeovers, session hijacking, or credential leaks. Boilerplate auth (NextAuth, Supabase Auth, Clerk) is tested by millions of users. For 95% of SaaS products, use a boilerplate. The 5% exception: highly specific auth requirements.
Key Takeaways
- Sessions from scratch: 2-3 days + security audit
- OAuth (Google/GitHub): 2-3 days per provider
- Password hashing: 1 day (easy to get wrong)
- CSRF protection: 1 day (often forgotten)
- Magic links: 2 days
- 2FA/TOTP: 3-5 days
- Total auth from scratch: 10-20 days (well done)
What "Good Auth" Actually Involves
Most developers underestimate authentication complexity. Here's the complete surface area:
Password Authentication
// The obvious part: hash and verify passwords
import { hash, compare } from 'bcrypt';
async function createUser(email: string, password: string) {
const passwordHash = await hash(password, 12); // 12 rounds minimum
return db.user.create({ data: { email, passwordHash } });
}
async function verifyPassword(password: string, hash: string) {
return compare(password, hash);
}
// The non-obvious parts:
// 1. Rate limiting to prevent brute force
const rateLimiter = new RateLimiter({ max: 5, windowMs: 15 * 60 * 1000 });
// 2. Timing-safe comparison (prevents timing attacks)
import { timingSafeEqual } from 'crypto';
// 3. Password strength requirements (OWASP guidelines)
// 4. Breached password checking (HaveIBeenPwned API)
// 5. Account lockout after repeated failures
// 6. Secure password reset (time-limited tokens)
// 7. Token invalidation after use
// 8. Email enumeration prevention (same response for valid/invalid email)
Implementing all 8 of these correctly takes 3-5 days. Missing any creates vulnerabilities.
Session Management
// Sessions are more complex than JWT vs cookies
// Production session requirements:
// 1. Secure, httpOnly cookies (prevents XSS theft)
res.cookie('session', sessionToken, {
httpOnly: true, // Not accessible via JavaScript
secure: true, // HTTPS only
sameSite: 'lax', // CSRF protection
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
});
// 2. Session rotation on privilege escalation
// Regenerate session ID after login to prevent session fixation
async function login(req, user) {
const oldSessionId = req.sessionId;
await destroySession(oldSessionId); // Destroy old session
const newSession = await createSession(user.id); // Create new
return newSession.id;
}
// 3. Concurrent session handling
// 4. Session invalidation on password change
// 5. Session listing/revocation for users
// 6. Sliding vs fixed expiration
OAuth Integration
// Each OAuth provider has quirks:
// Google OAuth — token refresh, scopes, account selection
const googleProvider = {
authorizationUrl: 'https://accounts.google.com/o/oauth2/v2/auth',
params: {
access_type: 'offline', // Get refresh token
prompt: 'consent', // Always show consent screen (get refresh token)
scope: 'openid email profile',
},
async handleCallback(code) {
const tokens = await exchangeCode(code);
// Decode ID token (verify signature!)
const payload = verifyJWT(tokens.id_token, googlePublicKeys);
// Handle: email_verified might be false
if (!payload.email_verified) throw new Error('Email not verified');
return { email: payload.email, name: payload.name, id: payload.sub };
}
};
// GitHub OAuth — different email privacy settings
async function getGitHubEmail(accessToken: string) {
// Primary email might be private — must check /user/emails
const emails = await fetch('https://api.github.com/user/emails', {
headers: { Authorization: `Bearer ${accessToken}` }
}).then(r => r.json());
const primary = emails.find(e => e.primary && e.verified);
if (!primary) throw new Error('No verified primary email');
return primary.email;
}
Each provider has different quirks. NextAuth handles all of these.
What NextAuth (Used in Most Boilerplates) Gets Right
// NextAuth's session handling is correct by default:
export const authOptions: NextAuthOptions = {
session: {
strategy: 'jwt', // Or 'database' for server sessions
maxAge: 30 * 24 * 60 * 60, // 30 days
},
cookies: {
sessionToken: {
options: {
httpOnly: true, // XSS protection
sameSite: 'lax', // CSRF protection
path: '/',
secure: process.env.NODE_ENV === 'production',
}
}
},
callbacks: {
async signIn({ user, account, profile }) {
// Custom validation — return false to block sign in
if (!user.email) return false;
return true;
},
async jwt({ token, user }) {
if (user) {
token.id = user.id;
token.role = user.role;
}
return token;
},
async session({ session, token }) {
session.user.id = token.id as string;
session.user.role = token.role as string;
return session;
},
},
};
This is correct. Building this yourself from scratch means understanding all the security decisions implicitly made here.
When to Roll Your Own Auth
Build auth from scratch when:
- Non-standard auth protocol: Kerberos, LDAP/AD integration, custom token formats
- Regulated environment: HIPAA, FedRAMP, FIPS 140-2 requirements
- Platform product: You're building auth as a product for others (like Auth0 or Clerk themselves)
- Extreme performance: Millions of auth operations per second (unlikely for most SaaS)
For standard B2B or B2C SaaS, none of these apply.
Auth Provider Comparison: NextAuth vs Clerk vs Supabase Auth
| NextAuth | Clerk | Supabase Auth | |
|---|---|---|---|
| Self-hosted | ✅ | ❌ | ✅ (Supabase) |
| Pricing | Free | Free up to 10k users | Free tier |
| Complexity | Medium | Low | Low |
| Customization | High | Medium | Medium |
| Magic links | ✅ | ✅ | ✅ |
| 2FA | Via plugin | ✅ | ✅ |
| Org/Teams | ❌ (manual) | ✅ | ✅ (via RLS) |
| UI components | ❌ | ✅ prebuilt | ❌ |
Boilerplate defaults:
- ShipFast, T3 Stack, Epic Stack → NextAuth
- Supastarter → Supabase Auth
- Most commercial starters → Clerk or NextAuth
The Security Verdict
Using NextAuth, Clerk, or Supabase Auth from a reputable boilerplate:
✅ Tested by millions of users ✅ Security researchers review the code ✅ Vulnerabilities patched quickly ✅ Correct session, cookie, and CSRF handling by default
Building from scratch:
⚠️ You are the security researcher ⚠️ Vulnerabilities discovered by your users (or attackers) ⚠️ Every security decision is yours to get right
For SaaS products where auth is not a competitive differentiator, the choice is clear.
Find boilerplates with the best auth implementations on StarterPick.
Check out this boilerplate
View ShipFast on StarterPick →