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)

javascript
async 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)

javascript
import 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

jsx
import { 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

javascript
import 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

javascript
import 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

javascript
function 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 };
}

Learn More