Production-Ready Authentication Patterns for Modern SaaS
Authentication is the foundation of any SaaS application. Get it wrong, and you're vulnerable to attacks. Get it right, and users trust your platform. This guide covers the authentication patterns we've battle-tested across hundreds of production deployments.
JWT vs. Session-Based Authentication
The choice between JWT and session-based authentication isn't just about preference—it's about your architecture. JWTs are stateless, making them ideal for microservices and distributed systems. Sessions require server-side storage but offer better revocation control.
For most SaaS applications, we recommend JWTs with short expiration times (15 minutes) and refresh tokens stored securely in HTTP-only cookies. This gives you the benefits of statelessness while maintaining security through token rotation.
// JWT token structure
{
"userId": "user_123",
"email": "user@example.com",
"role": "user",
"iat": 1638360000,
"exp": 1638360900 // 15 minutes
}
// Refresh token (stored in HTTP-only cookie)
{
"userId": "user_123",
"tokenId": "refresh_abc123",
"exp": 1638446400 // 24 hours
}Password Reset Flow
A secure password reset flow requires multiple safeguards. Never expose user existence, use time-limited tokens, and implement rate limiting. Here's our production pattern:
- User requests reset: Always return the same message whether the email exists or not. This prevents email enumeration attacks.
- Generate secure token: Use crypto.randomBytes(32) to create a 256-bit token. Store it hashed in the database with an expiration (typically 1 hour).
- Send reset email: Include the token in a secure link. Never include it in the URL path—use query parameters that are logged.
- Validate and reset: Check token expiration, verify the hash, then allow password change. Immediately invalidate the token after use.
// Password reset token generation
import crypto from 'crypto';
const resetToken = crypto.randomBytes(32).toString('hex');
const hashedToken = crypto
.createHash('sha256')
.update(resetToken)
.digest('hex');
// Store in database with expiration
await db.passwordResets.create({
userId: user.id,
token: hashedToken,
expiresAt: new Date(Date.now() + 3600000) // 1 hour
});Email Verification
Email verification prevents fake accounts and ensures you have valid contact information. The pattern is similar to password reset but with different security considerations.
Key differences: verification tokens can be longer-lived (24-48 hours), and you should allow re-sending verification emails. Track verification attempts to prevent abuse.
Social Login Integration
OAuth 2.0 is the standard for social login, but implementation details matter. Use Passport.js or similar libraries, but understand what they're doing under the hood. Always verify the ID token server-side, never trust client-side claims.
When a user signs in with Google, GitHub, or another provider, create a linked account record. This allows users to connect multiple providers to the same account, improving UX while maintaining security.
Multi-Factor Authentication (MFA)
MFA adds an extra layer of security by requiring a second factor. TOTP (Time-based One-Time Password) apps like Google Authenticator are the most common. SMS-based 2FA is less secure but more accessible.
Implementation requires generating a secret, displaying a QR code, and verifying codes on login. Store backup codes securely and allow users to regenerate them. Our templates include MFA-ready patterns that you can enable when needed.
Security Best Practices
- •Rate limiting: Limit login attempts to 5 per 15 minutes per IP. Use exponential backoff for repeated failures.
- •Password requirements: Minimum 8 characters, but don't enforce complexity rules. Use zxcvbn for strength estimation instead.
- •Token rotation: Rotate refresh tokens on use. This limits the damage if a token is compromised.
- •Audit logging: Log all authentication events—logins, logouts, password changes, MFA enrollment. This helps with security investigations.
Conclusion
Authentication is complex, but following these patterns ensures your SaaS application is secure by default. Our Auth Starter template implements all of these patterns, giving you a production-ready foundation you can customize.
Remember: security is not a feature you add later. It must be built into your authentication system from day one. Start with our templates, and you'll have these patterns working out of the box.
Ready to Ship Your SaaS?
Stop writing the same boilerplate code. Browse our production-ready SaaS templates — each includes authentication, Stripe billing, database setup, and deployment configs so you can launch in days instead of months.
Check out our straightforward pricing, see how we compare to ShipFast and MakerKit, or read the documentation to understand exactly what you get. Questions? Learn about our team and mission, or catch up on our engineering blog.