Authentication
Authentication is the process of verifying the identity of a user, device, or system. In web development, it answers the question: "Who are you?"
Authentication vs Authorization
- Authentication: Verifying identity ("Who are you?")
- Authorization: Granting permissions ("What can you do?")
javascript// Authentication: User proves they are [email protected] login('[email protected]', 'password123'); // Authorization: Check if [email protected] can delete posts if (user.role === 'admin') { deletePost(postId); }
Common Authentication Methods
1. Email/Password
The most common authentication method.
javascript// Sign up async function signUp(email, password) { const hashedPassword = await hashPassword(password); const user = await db.users.create({ email, password: hashedPassword }); return user; } // Login async function login(email, password) { const user = await db.users.findOne({ email }); if (!user) { throw new Error('User not found'); } const isValid = await comparePassword(password, user.password); if (!isValid) { throw new Error('Invalid password'); } const token = generateToken(user.id); return { user, token }; }
2. Social OAuth (Google, GitHub, etc.)
javascript// Using NextAuth.js import NextAuth from 'next-auth'; import GoogleProvider from 'next-auth/providers/google'; import GitHubProvider from 'next-auth/providers/github'; export default NextAuth({ providers: [ GoogleProvider({ clientId: process.env.GOOGLE_ID, clientSecret: process.env.GOOGLE_SECRET, }), GitHubProvider({ clientId: process.env.GITHUB_ID, clientSecret: process.env.GITHUB_SECRET, }), ], callbacks: { async session({ session, token }) { session.user.id = token.sub; return session; }, }, });
3. Magic Links (Passwordless)
javascriptasync function sendMagicLink(email) { const token = generateToken({ email }, '15m'); const magicLink = `https://yourapp.com/auth/verify?token=${token}`; await sendEmail({ to: email, subject: 'Login to Your App', body: `Click here to login: ${magicLink}` }); } async function verifyMagicLink(token) { try { const { email } = verifyToken(token); const user = await db.users.findOne({ email }); const sessionToken = generateToken(user.id); return { user, token: sessionToken }; } catch (error) { throw new Error('Invalid or expired link'); } }
4. Multi-Factor Authentication (MFA)
javascript// Step 1: Username/password const user = await login(email, password); // Step 2: Send OTP const otp = generateOTP(); await db.otps.create({ userId: user.id, code: otp, expiresAt: Date.now() + 5 * 60 * 1000 // 5 minutes }); await sendSMS(user.phone, `Your code is: ${otp}`); // Step 3: Verify OTP async function verifyOTP(userId, code) { const otp = await db.otps.findOne({ userId, code, expiresAt: { $gt: Date.now() } }); if (!otp) { throw new Error('Invalid or expired code'); } await db.otps.delete({ userId }); return generateToken(userId); }
Token-Based Authentication
JWT (JSON Web Tokens)
javascriptimport jwt from 'jsonwebtoken'; // Generate token function generateToken(userId) { return jwt.sign( { userId }, process.env.JWT_SECRET, { expiresIn: '7d' } ); } // Verify token function verifyToken(token) { try { return jwt.verify(token, process.env.JWT_SECRET); } catch (error) { throw new Error('Invalid token'); } } // Middleware function authMiddleware(req, res, next) { const token = req.headers.authorization?.replace('Bearer ', ''); if (!token) { return res.status(401).json({ error: 'No token provided' }); } try { const decoded = verifyToken(token); req.userId = decoded.userId; next(); } catch (error) { return res.status(401).json({ error: 'Invalid token' }); } }
Session-Based Authentication
javascript// Express with express-session import session from 'express-session'; app.use(session({ secret: process.env.SESSION_SECRET, resave: false, saveUninitialized: false, cookie: { secure: true, // HTTPS only httpOnly: true, // No JS access maxAge: 24 * 60 * 60 * 1000 // 24 hours } })); // Login route app.post('/login', async (req, res) => { const { email, password } = req.body; const user = await validateCredentials(email, password); if (user) { req.session.userId = user.id; res.json({ success: true }); } else { res.status(401).json({ error: 'Invalid credentials' }); } }); // Protected route app.get('/profile', (req, res) => { if (!req.session.userId) { return res.status(401).json({ error: 'Not authenticated' }); } const user = db.users.findById(req.session.userId); res.json(user); }); // Logout app.post('/logout', (req, res) => { req.session.destroy(); res.json({ success: true }); });
Client-Side Authentication
React Context
jsximport { createContext, useState, useContext, useEffect } from 'react'; const AuthContext = createContext(); export function AuthProvider({ children }) { const [user, setUser] = useState(null); const [loading, setLoading] = useState(true); useEffect(() => { // Check for existing session checkAuth(); }, []); async function checkAuth() { try { const response = await fetch('/api/auth/me', { headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` } }); if (response.ok) { const data = await response.json(); setUser(data.user); } } catch (error) { console.error('Auth check failed:', error); } finally { setLoading(false); } } async function login(email, password) { const response = await fetch('/api/auth/login', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email, password }) }); const data = await response.json(); if (response.ok) { localStorage.setItem('token', data.token); setUser(data.user); return true; } throw new Error(data.error); } function logout() { localStorage.removeItem('token'); setUser(null); } return ( <AuthContext.Provider value={{ user, login, logout, loading }}> {children} </AuthContext.Provider> ); } export function useAuth() { return useContext(AuthContext); } // Usage function LoginPage() { const { login } = useAuth(); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); async function handleSubmit(e) { e.preventDefault(); try { await login(email, password); router.push('/dashboard'); } catch (error) { alert(error.message); } } return ( <form onSubmit={handleSubmit}> <input type="email" value={email} onChange={e => setEmail(e.target.value)} /> <input type="password" value={password} onChange={e => setPassword(e.target.value)} /> <button type="submit">Login</button> </form> ); } // Protected route function ProtectedPage() { const { user, loading } = useAuth(); if (loading) return <div>Loading...</div>; if (!user) return <Navigate to="/login" />; return <div>Welcome, {user.name}!</div>; }
Next.js Middleware
javascript// middleware.ts import { NextResponse } from 'next/server'; export function middleware(request) { const token = request.cookies.get('auth-token'); if (!token) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); } export const config = { matcher: ['/dashboard/:path*', '/profile/:path*'] };
Security Best Practices
Password Hashing
javascriptimport bcrypt from 'bcrypt'; // Hash password async function hashPassword(password) { const saltRounds = 10; return bcrypt.hash(password, saltRounds); } // Compare password async function comparePassword(password, hash) { return bcrypt.compare(password, hash); }
Secure Token Storage
javascript// Good: HttpOnly cookie (server-side) res.cookie('auth-token', token, { httpOnly: true, // No JavaScript access secure: true, // HTTPS only sameSite: 'strict', // CSRF protection maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days }); // Avoid: localStorage (vulnerable to XSS) localStorage.setItem('token', token); // Not recommended
Rate Limiting
javascriptimport rateLimit from 'express-rate-limit'; const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts message: 'Too many login attempts, please try again later' }); app.post('/login', loginLimiter, loginHandler);
Popular Auth Libraries
- NextAuth.js: Authentication for Next.js
- Passport.js: Node.js authentication middleware
- Auth0: Authentication as a service
- Firebase Auth: Google's authentication solution
- Supabase Auth: Open-source Firebase alternative
- Clerk: Modern authentication platform
Common Patterns
Remember Me
javascriptfunction login(email, password, rememberMe) { const expiresIn = rememberMe ? '30d' : '1d'; const token = generateToken({ userId }, expiresIn); return token; }
Refresh Tokens
javascript// Short-lived access token + long-lived refresh token async function login(email, password) { const user = await validateCredentials(email, password); const accessToken = generateToken(user.id, '15m'); const refreshToken = generateToken(user.id, '7d'); await db.refreshTokens.create({ userId: user.id, token: refreshToken }); return { accessToken, refreshToken }; } // Refresh access token async function refresh(refreshToken) { const stored = await db.refreshTokens.findOne({ token: refreshToken }); if (!stored) { throw new Error('Invalid refresh token'); } const accessToken = generateToken(stored.userId, '15m'); return { accessToken }; }