🟫 ClodHost beta
Sign In
🎉 All services free during beta! 🎉 All services free during beta!

User Authentication

Add secure login, signup, and session management to your app. Protect routes and keep user data safe.

Reading time: 25 min Difficulty: Intermediate
View examples as:
Ask Claude Manual Code

Table of Contents

Authentication Overview

Authentication is how your app knows who a user is. There are several approaches:

Recommendation

For most web apps, session-based auth is simplest and most secure. Use JWT for APIs that need stateless authentication. Add OAuth for convenience.

User Registration (Signup)

Let users create accounts with email and password.

Signup Form & Backend

Example prompt:

"Create a signup system for my app. I need a signup form with email, password, and confirm password fields. Validate that passwords match and are at least 8 characters. Hash the password with bcrypt before storing. After signup, automatically log the user in and redirect to the dashboard."
// routes/auth.js
const express = require('express');
const bcrypt = require('bcrypt');
const { query } = require('../db');

const router = express.Router();

router.post('/signup', async (req, res) => {
    const { email, password, confirmPassword } = req.body;

    // Validation
    if (password !== confirmPassword) {
        return res.status(400).json({ error: 'Passwords do not match' });
    }
    if (password.length < 8) {
        return res.status(400).json({ error: 'Password must be at least 8 characters' });
    }

    // Hash password
    const passwordHash = await bcrypt.hash(password, 10);

    try {
        const result = await query(
            'INSERT INTO users (email, password_hash) VALUES (?, ?)',
            [email, passwordHash]
        );

        // Create session
        req.session.userId = result.insertId;
        res.json({ success: true, redirect: '/dashboard' });
    } catch (error) {
        if (error.code === 'ER_DUP_ENTRY') {
            res.status(400).json({ error: 'Email already registered' });
        } else {
            res.status(500).json({ error: 'Server error' });
        }
    }
});

Login & Sessions

Authenticate users and maintain their logged-in state.

Login System

Example prompt:

"Create a login system with email and password. Use express-session to manage sessions stored in the database. Include a 'remember me' checkbox that extends the session to 30 days. Show appropriate error messages for wrong email or password (but don't reveal which one was wrong for security)."
// Session setup in app.js
const session = require('express-session');
const MySQLStore = require('express-mysql-session')(session);

app.use(session({
    secret: process.env.SESSION_SECRET,
    store: new MySQLStore({ /* db options */ }),
    resave: false,
    saveUninitialized: false,
    cookie: {
        secure: process.env.NODE_ENV === 'production',
        httpOnly: true,
        maxAge: 24 * 60 * 60 * 1000 // 1 day default
    }
}));

// Login route
router.post('/login', async (req, res) => {
    const { email, password, rememberMe } = req.body;

    const users = await query(
        'SELECT * FROM users WHERE email = ?',
        [email]
    );

    if (users.length === 0) {
        return res.status(401).json({ error: 'Invalid credentials' });
    }

    const user = users[0];
    const validPassword = await bcrypt.compare(password, user.password_hash);

    if (!validPassword) {
        return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Create session
    req.session.userId = user.id;

    if (rememberMe) {
        req.session.cookie.maxAge = 30 * 24 * 60 * 60 * 1000; // 30 days
    }

    res.json({ success: true, redirect: '/dashboard' });
});

Logout

Example prompt:

"Add a logout endpoint that destroys the user's session and redirects to the home page. Also add a logout button to the header on all authenticated pages."
router.post('/logout', (req, res) => {
    req.session.destroy((err) => {
        if (err) {
            return res.status(500).json({ error: 'Could not log out' });
        }
        res.clearCookie('connect.sid');
        res.json({ success: true, redirect: '/' });
    });
});

Protected Routes

Restrict access to certain pages for logged-in users only.

Authentication Middleware

Example prompt:

"Create an authentication middleware that checks if a user is logged in. If not, redirect to /login for page requests or return a 401 error for API requests. Apply this middleware to protect the /dashboard, /settings, and /profile routes."
// middleware/auth.js
function requireAuth(req, res, next) {
    if (!req.session.userId) {
        // Check if it's an API request
        if (req.xhr || req.headers.accept?.includes('application/json')) {
            return res.status(401).json({ error: 'Not authenticated' });
        }
        // Redirect to login for page requests
        return res.redirect('/login?redirect=' + req.originalUrl);
    }
    next();
}

// Apply to routes
app.use('/dashboard', requireAuth, dashboardRouter);
app.use('/settings', requireAuth, settingsRouter);
app.use('/profile', requireAuth, profileRouter);

// Or apply to specific routes
app.get('/api/user', requireAuth, (req, res) => {
    // Only accessible if logged in
});

Password Reset

Let users recover their accounts when they forget their password.

Password Reset Flow

Example prompt:

"Create a password reset system. When a user requests a reset, generate a secure random token that expires in 1 hour. Send an email with a reset link. When they click the link, show a form to enter a new password. After reset, invalidate the token and log them in."
const crypto = require('crypto');

// Request password reset
router.post('/forgot-password', async (req, res) => {
    const { email } = req.body;

    const user = await getUserByEmail(email);
    if (!user) {
        // Don't reveal if email exists
        return res.json({ message: 'If that email exists, we sent a reset link' });
    }

    // Generate secure token
    const token = crypto.randomBytes(32).toString('hex');
    const expires = new Date(Date.now() + 3600000); // 1 hour

    await query(
        'UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?',
        [token, expires, user.id]
    );

    // Send email with reset link
    await sendEmail(email, 'Password Reset',
        `Click here to reset your password: https://yourapp.com/reset?token=${token}`
    );

    res.json({ message: 'If that email exists, we sent a reset link' });
});

// Reset password with token
router.post('/reset-password', async (req, res) => {
    const { token, newPassword } = req.body;

    const users = await query(
        'SELECT * FROM users WHERE reset_token = ? AND reset_expires > NOW()',
        [token]
    );

    if (users.length === 0) {
        return res.status(400).json({ error: 'Invalid or expired token' });
    }

    const user = users[0];
    const passwordHash = await bcrypt.hash(newPassword, 10);

    await query(
        'UPDATE users SET password_hash = ?, reset_token = NULL WHERE id = ?',
        [passwordHash, user.id]
    );

    req.session.userId = user.id;
    res.json({ success: true, redirect: '/dashboard' });
});

Social Login (OAuth)

Let users sign in with their existing accounts from Google, GitHub, etc.

Google OAuth Setup

Example prompt:

"Add Google OAuth login to my app using Passport.js. When a user logs in with Google for the first time, create an account using their Google email and profile picture. If they already have an account with that email, link the Google account. Store the Google ID for future logins."
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: '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
    try {
        // Check if user exists
        let user = await query(
            'SELECT * FROM users WHERE google_id = ? OR email = ?',
            [profile.id, profile.emails[0].value]
        );

        if (user.length === 0) {
            // Create new user
            const result = await query(
                'INSERT INTO users (email, name, google_id, avatar) VALUES (?, ?, ?, ?)',
                [profile.emails[0].value, profile.displayName, profile.id, profile.photos[0].value]
            );
            user = { id: result.insertId };
        } else {
            // Link Google account if needed
            if (!user[0].google_id) {
                await query('UPDATE users SET google_id = ? WHERE id = ?', [profile.id, user[0].id]);
            }
            user = user[0];
        }

        done(null, user);
    } catch (error) {
        done(error);
    }
}));

// Routes
app.get('/auth/google', passport.authenticate('google', { scope: ['profile', 'email'] }));
app.get('/auth/google/callback', passport.authenticate('google'), (req, res) => {
    res.redirect('/dashboard');
});

Security Best Practices

Critical Security Rules

Never skip these. A single vulnerability can expose all your users' data.

Rate Limiting

Example prompt:

"Add rate limiting to protect against brute force attacks. Limit login attempts to 5 per minute per IP address. After 5 failed attempts for the same email, lock the account for 15 minutes. Show appropriate error messages."
const rateLimit = require('express-rate-limit');

const loginLimiter = rateLimit({
    windowMs: 60 * 1000, // 1 minute
    max: 5, // 5 attempts per minute
    message: { error: 'Too many login attempts. Please try again later.' },
    standardHeaders: true,
    legacyHeaders: false,
});

app.use('/api/login', loginLimiter);