Auth from Scratch vs Boilerplate in 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.
The Real Cost of Getting Auth Wrong
The argument for building auth from scratch often sounds like engineering virtue — "we'll have full control, full understanding, no third-party dependency." What it consistently underestimates is the cost of getting it wrong.
Auth vulnerabilities are almost invisible until they're exploited. A misconfigured CSRF protection that accepts cross-origin requests looks like a working login form. A session token that doesn't rotate on privilege escalation works correctly in normal use and silently allows session fixation attacks. Password reset tokens that don't expire function perfectly for 99.9% of your users and expose the 0.1% who request a reset and don't use it immediately.
These aren't theoretical risks. OWASP's Top 10 consistently lists authentication failures in the first three positions because they are common, consequential, and hard to catch in code review. The developers who built your favorite SaaS boilerplate have fixed these exact bugs in production based on security researcher reports and real-world incidents.
When you use NextAuth, Clerk, or Supabase Auth, you are not just getting the happy-path authentication flow. You are getting the hardened version that survived contact with real attack patterns. That is worth a lot more than the flexibility of rolling your own.
The Hidden Day Count in "Building Auth from Scratch"
The 10-20 day estimate in the TL;DR is accurate but often challenged by developers who think "I can build a basic login in a day." That's true. Here's what the other 19 days cover:
Days 1-2: Basic email/password flow with bcrypt hashing. This is the part developers estimate for.
Days 3-4: Session management — generating tokens, storing them securely, handling expiry, refreshing on activity, handling simultaneous sessions.
Days 5-6: CSRF protection, cookie security settings (httpOnly, Secure, SameSite), and testing that they actually work.
Days 7-9: Password reset flow — generating time-limited tokens, single-use enforcement, email enumeration prevention (returning the same response whether the email exists or not), invalidating all sessions on password change.
Days 10-12: OAuth integration — one provider is a day, but you need Google and GitHub at minimum. Each has quirks (GitHub private emails, Google refresh token requirements).
Days 13-14: Magic links — a simpler alternative to passwords, but still requires the token generation, expiry, and email delivery infrastructure.
Days 15-17: Rate limiting — brute force prevention on login, signup, and password reset endpoints. Getting the limits right (not too tight to block legitimate users, not too loose to allow attacks) requires iteration.
Days 18-19: Security audit pass — reviewing every decision made above against OWASP guidelines, getting a second developer to adversarially review the implementation.
Day 20: Documentation so future maintainers understand the security decisions and don't accidentally remove protections while "simplifying" the code.
That's what it actually takes. Every day you skip is a potential vulnerability you've left open. Most teams who build auth from scratch actually take 5-7 days, ship without the remaining 13-15 days of work, and discover which parts they missed in production.
Evaluating Boilerplate Auth: What to Look For
Not all boilerplate auth implementations are equally good. When evaluating a boilerplate specifically for its auth quality, here's what to check:
Cookie configuration: Find where session cookies are set and confirm httpOnly: true, secure: true in production, and sameSite: 'lax' or 'strict'. These three settings prevent the most common session-based attacks.
Token handling: Password reset and email verification tokens should be stored as hashed values in the database (not plaintext), should have an expiry (15-60 minutes is standard), and should be single-use (invalidated immediately after use).
Error messages: Check that login errors don't reveal whether the email exists. The correct pattern is a generic "Invalid email or password" message regardless of which was wrong. The wrong pattern is "Email not found" or "Incorrect password" (these enable account enumeration).
Rate limiting: Verify that the login endpoint has rate limiting. If you can make 1,000 login attempts per minute with no pushback, brute force is trivial.
The comparison of Better Auth vs Clerk vs NextAuth in 2026 evaluates each of these properties across the major auth solutions used in popular boilerplates.
Multi-Tenancy and Auth: The Complication
Standard auth — single user, single account — is the case that NextAuth and Clerk handle perfectly out of the box. The complexity escalates when your product needs teams.
Team auth requires answers to questions that don't come up in user-level auth:
Who can invite new members? Only the workspace owner, or any existing member? What happens when an invited user signs up with a different email than the invitation was sent to? Can a user be a member of multiple workspaces simultaneously, and if so, how do you manage which workspace is "active" in their session?
What happens to workspace resources when the workspace owner deletes their account? Can a non-owner transfer ownership? What's the grace period on subscription cancellation when the paying owner leaves?
These aren't auth problems in the narrow technical sense, but they live at the intersection of auth, billing, and data ownership. Boilerplates that have multi-tenancy built in (Makerkit, Supastarter) have thought through these edge cases and implemented them. Boilerplates without org support will leave you answering these questions yourself.
If your product is B2B and will need team accounts, start with a boilerplate that already has org-level auth. The best SaaS boilerplates for 2026 includes a breakdown of which starters have multi-tenant auth ready.
When Managed Auth (Clerk) Is Worth the Price
Clerk's pricing — free up to 10,000 monthly active users, then roughly $0.02 per MAU — is the most common reason teams consider self-hosting auth instead. At 50,000 MAU, Clerk costs about $800/month. At 200,000 MAU, it's around $3,800/month.
For most SaaS products at those scale levels, this is rational pricing. You're paying to have a team of security engineers maintain, monitor, and patch your auth infrastructure. The alternative — your engineers doing that work — almost certainly costs more in engineering time.
The calculus changes at the edges. If your product is a freemium consumer app where most MAUs are low-value free users (high user count, low revenue per user), Clerk's per-MAU pricing becomes expensive relative to the revenue those users generate. This is the scenario where NextAuth (self-hosted, free) becomes the right choice — you pay in engineering complexity, not vendor bills.
If your product has compliance requirements (HIPAA, SOC 2, GDPR), evaluate whether Clerk or Auth0 has the compliance posture your customers require. Self-hosting auth to avoid vendor compliance dependencies is a legitimate reason to build your own, as long as you understand the security maintenance burden you're taking on.
The ShipFast review for 2026 covers how ShipFast's default NextAuth setup handles these tradeoffs and what you'd need to change for different scale scenarios.
Find boilerplates with the best auth implementations on StarterPick.
Review ShipFast and compare alternatives on StarterPick.