Skip to content

Latest commit

 

History

History
283 lines (195 loc) · 8.29 KB

File metadata and controls

283 lines (195 loc) · 8.29 KB

Let’s go much deeper into Authentication in Express.js. I’ll break it down into concepts, architecture, implementation details, advanced use-cases, security considerations, and best practices. We’ll go beyond just simple JWT/session examples and cover everything you need to truly understand and implement authentication professionally.


Authentication in Express.js

Authentication is more than just verifying a password—it’s about securely managing user identity, sessions, tokens, roles, and permissions.


1️⃣ Authentication vs Authorization

It’s critical to understand the difference:

Concept Definition Example in Express.js
Authentication Verifying who the user is Logging in with email & password, OAuth login
Authorization Determining what the user can do Checking if user has 'admin' role to access /admin route

Authentication always comes before authorization.


2️⃣ Authentication Architectures

a) Session-based Authentication (Stateful)

  • The server keeps track of logged-in users.
  • Uses server-side storage: memory, database, Redis.
  • Typical workflow:
  1. User logs in.
  2. Server creates a session ID and stores it in memory/db.
  3. Sends a cookie with session ID to client.
  4. Client sends cookie with each request.
  5. Server validates session ID → grants access.

Pros:

  • Easy to implement.
  • Can invalidate sessions anytime.

Cons:

  • Not scalable for APIs / microservices without shared session storage (like Redis).

Express Example Packages: express-session, connect-mongo, connect-redis.


b) Token-based Authentication (Stateless)

  • Server does not store session; everything is encoded in a token (usually JWT).
  • Workflow:
  1. User logs in.
  2. Server generates JWT → signs it with secret/private key.
  3. Client stores token (cookie, localStorage).
  4. Client sends token in Authorization header with requests.
  5. Server verifies token → grants access.

Pros:

  • Scales easily (stateless).
  • Ideal for mobile apps and SPAs.

Cons:

  • Cannot invalidate token easily until expiration unless you use a token blacklist.

c) OAuth / Social Login

  • Delegate authentication to providers: Google, Facebook, GitHub.

  • Uses OAuth2 tokens.

  • Express middleware like passport-google-oauth20 is commonly used.

  • Typical flow:

    1. User clicks “Login with Google”.
    2. Redirects to Google login page.
    3. Google redirects back with a code.
    4. Server exchanges code for access token → optionally creates a local user.

3️⃣ Password Handling

Never store plain passwords. Best practices:

  1. Hash passwords with bcrypt/scrypt/argon2.
  2. Use salt to prevent rainbow table attacks.
  3. Consider pepper (a secret string stored in server, appended to password before hashing).
import bcrypt from "bcryptjs";

const hashPassword = async (password) => {
  const salt = await bcrypt.genSalt(12); // 12 rounds
  return await bcrypt.hash(password, salt);
};

**4️⃣ JWT **

JWT = JSON Web Token, a stateless authentication mechanism.

Structure:

Header.Payload.Signature
  • Header: { "alg": "HS256", "typ": "JWT" }
  • Payload: { "id": "userId", "role": "admin" }
  • Signature: HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)

JWT Advantages:

  • Stateless → scalable.
  • Can include roles, permissions in payload.
  • Works for APIs, SPAs, microservices.

JWT Disadvantages:

  • Cannot revoke easily until expiration.
  • If secret leaks → all tokens compromised.
  • Must always use HTTPS.

Best Practices:

  1. Short-lived tokens (5-15 min)
  2. Refresh tokens for long sessions
  3. Store token in HTTP-only cookie to prevent XSS
  4. Validate signature on every request

5️⃣ Protecting Routes

Middleware pattern:

import jwt from "jsonwebtoken";

export const authenticate = (roles = []) => (req, res, next) => {
  const token = req.cookies.token || req.header("Authorization")?.replace("Bearer ", "");

  if (!token) return res.status(401).json({ message: "No token provided" });

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;

    if (roles.length && !roles.includes(decoded.role))
      return res.status(403).json({ message: "Forbidden" });

    next();
  } catch (err) {
    return res.status(401).json({ message: "Invalid token" });
  }
};

Explanation:

  • roles parameter allows authorization along with authentication.
  • Middleware checks token existence, validity, and optionally role.

6️⃣ Refresh Tokens (Advanced JWT Handling)

Problem: JWT expires quickly → user must login again. Solution: Refresh tokens:

  1. Access token → short-lived (5-15 min)

  2. Refresh token → long-lived (7-30 days)

  3. Workflow:

    • Access token expires → client sends refresh token → server validates → issues new access token.
    • Store refresh token securely (HTTP-only cookie or DB)

Express Implementation Concept:

// /token route
router.post("/token", async (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.status(401).json({ message: "No refresh token" });

  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_SECRET);
    const newAccessToken = jwt.sign({ id: decoded.id }, process.env.JWT_SECRET, { expiresIn: "15m" });
    res.cookie("token", newAccessToken, { httpOnly: true });
    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ message: "Invalid refresh token" });
  }
});

7️⃣ Multi-Factor Authentication (MFA)

  • Adds an extra layer of security.
  • Example: after login, send an OTP (via email or SMS) and verify before granting access.
  • Packages: speakeasy (for TOTP), nodemailer (for email), twilio (for SMS).

Workflow:

  1. Login with password
  2. Server generates TOTP → sends to user
  3. User enters TOTP
  4. Server verifies → issues session/token

8️⃣ Security Considerations

  1. Always hash passwords and never store plain text.
  2. Use HTTPS to prevent MITM attacks.
  3. HTTP-only cookies → prevent XSS stealing of JWT.
  4. CSRF protection for cookies: use CSRF tokens.
  5. Rate-limit login endpoints → prevent brute force.
  6. Avoid verbose error messages → attackers shouldn’t know if email exists.
  7. Implement account lockout or CAPTCHA after multiple failed attempts.
  8. Use helmet.js in Express → set secure headers.

9️⃣ Use Cases / Advanced Scenarios

Scenario Recommended Approach
Traditional web app Session-based + cookie
SPA (React, Vue) JWT + HTTP-only cookie
API for mobile app JWT + refresh token
Multi-role app JWT + roles + middleware
Social login OAuth2 + JWT/session
High-security app MFA + short-lived tokens + monitoring

10️⃣ Example Project Architecture

/project
  /models
    User.js
  /routes
    auth.js
    user.js
  /middleware
    auth.js
  server.js
  config.js
  • User.js → user schema, password hashing
  • auth.js → login, register, refresh token, logout
  • user.js → protected routes (dashboard, profile)
  • middleware/auth.js → verify token + roles
  • server.js → main Express app setup

Summary

Authentication in Express.js is multi-layered:

  1. User identity verification → password/email, OAuth, token
  2. Session or token storage → stateful vs stateless
  3. Route protection → middleware checks identity & role
  4. Advanced features → refresh tokens, MFA, rate limiting
  5. Security best practices → hashing, HTTPS, CSRF, XSS prevention

With this deep dive, you now know how authentication works under the hood, how to implement it in Express.js securely, and what real-world use cases look like.