How to Add User Authentication to Your Web App (Google & Email Login)
Complete guide to implementing user authentication with Google Sign-In and email/password login. Security best practices and code examples included.
User authentication is one of the most critical features of modern web applications. Whether you're building a SaaS product, social platform, or membership site, you need a way for users to create accounts, log in securely, and access their data.
This guide will walk you through everything you need to know about implementing authentication, from basic concepts to production-ready implementation.
What is User Authentication?
Authentication is the process of verifying that users are who they claim to be. It typically involves:
- Registration - Users create an account
- Login - Users prove their identity
- Session management - Keeping users logged in
- Authorization - Controlling what users can access
Why Authentication Matters
Security
- Protect user data
- Prevent unauthorized access
- Comply with regulations (GDPR, CCPA)
Personalization
- Save user preferences
- Display personalized content
- Track user activity
Business value
- Build user relationships
- Enable paid features
- Gather analytics
Authentication Methods Explained
Email/Password (Traditional)
How it works:
- User provides email and password
- Password is hashed and stored
- On login, password is verified
- Session is created
Pros:
- Users understand it
- No third-party dependency
- Complete control
Cons:
- Users forget passwords
- Security responsibility is yours
- Need password reset flow
- Vulnerable to attacks if not done right
Social Login (OAuth)
Popular providers:
- GitHub
- Twitter/X
- Apple
- Microsoft
How it works:
- User clicks "Sign in with Google"
- Redirected to Google
- User authorizes your app
- Google sends back user info
- You create/log in the user
Pros:
- Faster registration
- No password to manage
- Provider handles security
- Get user profile data
Cons:
- Depends on third party
- User needs provider account
- Privacy concerns for some
- Setup required for each provider
Magic Links (Passwordless)
How it works:
- User enters email
- You send login link
- User clicks link
- Automatically logged in
Pros:
- No passwords to forget
- Very secure
- Simple UX
Cons:
- Requires email access
- Can be slower
- Email delays
- Spam folder issues
Multi-Factor Authentication (MFA)
Add extra security layer:
- SMS code
- Authenticator app
- Email verification
- Biometrics
When to use: Sensitive data, financial apps, admin access
What You Need to Build
A complete authentication system needs:
-
Registration page
- Email/password form
- Validation
- Account creation
-
Login page
- Sign-in form
- Social login buttons
- "Forgot password" link
-
Password reset flow
- Reset request page
- Email with reset link
- New password page
-
Protected routes
- Check if user is logged in
- Redirect if not authenticated
-
User profile/dashboard
- Display user info
- Account settings
- Logout button
-
Session management
- Keep users logged in
- Automatic logout after inactivity
- Remember device
-
Database
- Store user accounts
- Hash passwords
- Store sessions
Traditional Implementation Approach
Step 1: Database Setup
Create a users table:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
email VARCHAR(255) UNIQUE NOT NULL,
password_hash VARCHAR(255) NOT NULL,
google_id VARCHAR(255),
created_at TIMESTAMP DEFAULT NOW(),
last_login TIMESTAMP
);
Step 2: Password Hashing
NEVER store plain text passwords!
const bcrypt = require('bcrypt');
// Registration
const saltRounds = 10;
const hashedPassword = await bcrypt.hash(password, saltRounds);
// Login verification
const isValid = await bcrypt.compare(password, user.password_hash);
Step 3: Registration Endpoint
app.post('/register', async (req, res) => {
const { email, password } = req.body;
// Validation
if (!email || !password) {
return res.status(400).json({ error: 'Email and password required' });
}
if (password.length < 8) {
return res.status(400).json({ error: 'Password must be 8+ characters' });
}
// Check if user exists
const existing = await db.query('SELECT * FROM users WHERE email = $1', [email]);
if (existing.rows.length > 0) {
return res.status(400).json({ error: 'Email already registered' });
}
// Hash password
const passwordHash = await bcrypt.hash(password, 10);
// Create user
const result = await db.query(
'INSERT INTO users (email, password_hash) VALUES ($1, $2) RETURNING id, email',
[email, passwordHash]
);
// Create session
req.session.userId = result.rows[0].id;
res.json({ user: result.rows[0] });
});
Step 4: Login Endpoint
app.post('/login', async (req, res) => {
const { email, password } = req.body;
// Find user
const result = await db.query('SELECT * FROM users WHERE email = $1', [email]);
const user = result.rows[0];
if (!user) {
return res.status(401).json({ error: 'Invalid email or password' });
}
// Verify password
const isValid = await bcrypt.compare(password, user.password_hash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid email or password' });
}
// Create session
req.session.userId = user.id;
// Update last login
await db.query('UPDATE users SET last_login = NOW() WHERE id = $1', [user.id]);
res.json({ user: { id: user.id, email: user.email } });
});
Step 5: Google OAuth Setup
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: "http://localhost:3000/auth/google/callback"
},
async (accessToken, refreshToken, profile, done) => {
// Find or create user
let user = await db.query('SELECT * FROM users WHERE google_id = $1', [profile.id]);
if (user.rows.length === 0) {
// Create new user
user = await db.query(
'INSERT INTO users (email, google_id) VALUES ($1, $2) RETURNING *',
[profile.emails[0].value, profile.id]
);
}
return done(null, user.rows[0]);
}
));
// Routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard');
}
);
Step 6: Protect Routes
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
next();
}
// Protected route
app.get('/dashboard', requireAuth, (req, res) => {
res.json({ message: 'Welcome to dashboard' });
});
Complexity: This is just the basics! You still need:
- Password reset flow
- Email verification
- CSRF protection
- Rate limiting
- Session security
- Error handling
- Frontend forms and validation
- Testing
Time to implement properly: 20-40 hours for someone experienced
Modern Authentication Services
Instead of building from scratch, use authentication-as-a-service:
Clerk
Features:
- Pre-built UI components
- Email/password & social login
- Session management
- User profiles
- Multi-factor authentication
Pricing:
- Free: 10,000 monthly active users
- Pro: $25/month + usage
Setup time: 15-30 minutes
Auth0
Features:
- Enterprise-grade
- Extensive customization
- Many authentication methods
- Advanced security
Pricing:
- Free: 7,500 active users
- Paid: Starts at $23/month
Supabase Auth
Features:
- Open source
- Built on PostgreSQL
- Row-level security
- Easy integration
Pricing:
- Free tier available
- Pro: $25/month
Firebase Authentication
Features:
- Google's solution
- Many providers
- Real-time integration
- Mobile + web
Pricing:
- Free tier generous
- Pay as you go
Security Best Practices
Password Requirements
✅ Do:
- Minimum 8 characters (12+ is better)
- Allow long passwords (64+ characters)
- Allow special characters
- Check against common passwords
- Use zxcvbn for strength checking
❌ Don't:
- Force complex requirements (users make weak patterns)
- Limit maximum length (prevents passphrases)
- Prevent paste (hurts password managers)
- Expire passwords arbitrarily
Secure Storage
NEVER:
- Store passwords in plain text
- Use MD5 or SHA1 for passwords
- Use weak salts
- Store sensitive data unencrypted
ALWAYS:
- Use bcrypt, scrypt, or Argon2
- Use unique salts per user
- Encrypt sensitive data
- Use HTTPS everywhere
Session Security
Best practices:
- Use secure, httpOnly cookies
- Implement CSRF protection
- Set appropriate session timeout
- Regenerate session ID after login
- Clear session on logout
Rate Limiting
Prevent brute force attacks:
const rateLimit = require('express-rate-limit');
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many login attempts, please try again later'
});
app.post('/login', loginLimiter, async (req, res) => {
// login logic
});
Input Validation
Always validate and sanitize:
const validator = require('validator');
// Email validation
if (!validator.isEmail(email)) {
return res.status(400).json({ error: 'Invalid email' });
}
// Sanitize input
const sanitizedEmail = validator.normalizeEmail(email);
Implementing with Modern Tools
Instead of writing all this code, describe what you need:
"Create user authentication with email/password and Google sign-in. Include registration, login, password reset, and protected dashboard. Use Clerk for authentication and style it to match my brand colors."
Modern AI tools can generate:
- Complete authentication flow
- Frontend components (login/register forms)
- Backend API routes
- Database schema
- Session management
- Protected routes
- All security best practices
Time: 15-30 minutes instead of days
Common Authentication Features
Email Verification
Force users to verify email before full access:
- Send verification email on registration
- Include unique token in link
- Mark email as verified when clicked
- Limit features until verified
Password Reset
Secure password reset flow:
- User requests reset
- Generate unique token
- Send email with reset link
- Token expires after time (1 hour)
- Allow new password setting
- Invalidate old sessions
Remember Me
Keep users logged in:
// Extend session duration
if (rememberMe) {
req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
} else {
req.session.cookie.maxAge = null; // Session cookie
}
Account Deletion
Allow users to delete accounts (GDPR requirement):
- Confirm user identity
- Warn about data loss
- Delete or anonymize data
- Send confirmation email
Testing Your Authentication
Before going live:
Security Testing
- Try SQL injection in forms
- Test XSS attacks
- Verify HTTPS everywhere
- Check password hashing
- Test session timeout
- Verify logout works
- Test CSRF protection
Functionality Testing
- Register new account
- Login with correct credentials
- Login with wrong password
- Login with non-existent email
- Google sign-in flow
- Password reset flow
- Email verification
- Access protected routes
- Logout
- Session persistence
Edge Cases
- Account already exists
- Concurrent logins
- Expired sessions
- Invalid tokens
- Network errors
- Database failures
Troubleshooting Common Issues
"Invalid credentials" on correct password
Causes:
- Password not hashed correctly
- Comparing wrong values
- Database encoding issues
Solution: Log hashes (temporarily) to debug
Sessions not persisting
Causes:
- Cookie not set
- HTTPS/domain issues
- Session store not configured
Solution: Check browser dev tools, verify session middleware
Google OAuth not working
Causes:
- Wrong redirect URI
- Invalid credentials
- Scope issues
Solution: Check Google Console settings, verify callback URL
"CSRF token mismatch"
Causes:
- Token not included in request
- Token expired
- Wrong configuration
Solution: Ensure token in forms, check middleware order
Going to Production
Production checklist:
Environment Variables
Never hardcode:
// .env file
DATABASE_URL=postgresql://...
SESSION_SECRET=long-random-string
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-secret
HTTPS
- Get SSL certificate (free with Let's Encrypt)
- Force HTTPS redirects
- Set secure cookie flags
Monitoring
Set up:
- Failed login alerts
- Account creation monitoring
- Session anomaly detection
- Error logging
Compliance
Consider:
- GDPR (if EU users)
- CCPA (if California users)
- Terms of service
- Privacy policy
- Cookie consent
Cost Comparison
Build It Yourself
Costs:
- Development time: 20-40 hours @ $50-150/hr = $1,000-$6,000
- Ongoing maintenance: 2-4 hours/month
- Security audits: $500-$2,000/year
- Total year 1: $1,500-$10,000
Use Authentication Service
Costs:
- Setup time: 2-4 hours
- Service fee: $0-$300/month
- Minimal maintenance
- Security included
- Total year 1: $0-$3,600
Savings: Thousands of dollars and weeks of time
Conclusion
Authentication is complex and critical. Building it from scratch requires:
- Deep security knowledge
- Significant development time
- Ongoing maintenance
- Regular security updates
Modern authentication services handle all of this for you, letting you focus on your core product instead of reinventing authentication.
Key takeaways:
- Never store plain text passwords
- Use HTTPS everywhere
- Consider authentication services
- Test security thoroughly
- Keep security updated
- Follow best practices
Whether you build it yourself or use a service, get authentication right. Your users' security depends on it.
Need user authentication in your app? OtterAI can generate a complete authentication system with email/password and social login, including all security best practices - describe your requirements and we'll build it for you.