Add secure login, signup, and session management to your app. Protect routes and keep user data safe.
Authentication is how your app knows who a user is. There are several approaches:
For most web apps, session-based auth is simplest and most secure. Use JWT for APIs that need stateless authentication. Add OAuth for convenience.
Let users create accounts with email and password.
// 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' });
}
}
});
Authenticate users and maintain their logged-in state.
// 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' });
});
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: '/' });
});
});
Restrict access to certain pages for logged-in users only.
// 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
});
Let users recover their accounts when they forget their password.
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' });
});
Let users sign in with their existing accounts from Google, GitHub, etc.
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');
});
Never skip these. A single vulnerability can expose all your users' data.
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);