Skip to content
Express.js interview 4 min read

Security & Auth Interview Questions

Security and authentication questions are where many Express interviews separate junior candidates from senior ones. Interviewers want to see that you understand the trade-offs between session and token auth, that you can name the common web attacks and their Express-level mitigations, and that you know how to handle passwords and CORS correctly. This page collects the most frequently asked questions with crisp, technically accurate answers and runnable Express code.

Sessions vs JWT: what’s the difference?

A common opening question. The honest answer is that they solve the same problem (proving who the user is on subsequent requests) with opposite trade-offs around state.

With server-side sessions, the server stores session data (in memory, Redis, or a database) and the client only holds an opaque session ID in a cookie. With JWT, the server signs a token containing claims and stores nothing; the token itself is the proof.

AspectSessionsJWT
Server stateYes (store required)Stateless
RevocationEasy — delete the sessionHard — needs a denylist or short TTL
ScalingNeeds shared store (Redis)Trivial across services
Payload visibilityHidden (opaque ID)Readable by anyone (base64, not encrypted)
Best forTraditional web appsAPIs, microservices, mobile

Common gotcha: JWTs are signed, not encrypted. Never put secrets or PII in the payload — anyone can decode it. Signing only guarantees integrity, not confidentiality.

import jwt from "jsonwebtoken";

function issueToken(user) {
  return jwt.sign(
    { sub: user.id, role: user.role },
    process.env.JWT_SECRET,
    { expiresIn: "15m" }
  );
}

function authenticate(req, res, next) {
  const header = req.headers.authorization || "";
  const token = header.startsWith("Bearer ") ? header.slice(7) : null;
  if (!token) return res.status(401).json({ error: "Missing token" });

  try {
    req.user = jwt.verify(token, process.env.JWT_SECRET);
    next();
  } catch {
    return res.status(401).json({ error: "Invalid or expired token" });
  }
}

How do you store passwords securely?

Never store plaintext or fast hashes (MD5/SHA-256). Use a slow, salted, adaptive hash such as bcrypt, scrypt, or Argon2. The slowness is the point — it makes brute-forcing expensive. bcrypt auto-generates and embeds a per-user salt.

import bcrypt from "bcrypt";

const SALT_ROUNDS = 12;

async function register(req, res) {
  const hash = await bcrypt.hash(req.body.password, SALT_ROUNDS);
  await db.users.insert({ email: req.body.email, passwordHash: hash });
  res.status(201).json({ message: "Created" });
}

async function login(req, res) {
  const user = await db.users.findOne({ email: req.body.email });
  const ok = user && (await bcrypt.compare(req.body.password, user.passwordHash));
  if (!ok) return res.status(401).json({ error: "Invalid credentials" });
  res.json({ token: issueToken(user) });
}

Tip: return the same generic “Invalid credentials” message whether the email is unknown or the password is wrong. Differentiating them leaks which accounts exist (user enumeration).

What is CSRF and how do you prevent it in Express?

Cross-Site Request Forgery tricks an authenticated user’s browser into sending an unwanted request to your site, relying on cookies being sent automatically. It only affects cookie-based auth — token-in-header APIs are immune because the browser won’t attach an Authorization header to a forged request.

Mitigations:

  • Set cookies with SameSite=Lax or Strict, plus HttpOnly and Secure.
  • Use a CSRF token (double-submit or synchronizer pattern) for state-changing requests.
import session from "express-session";
import { doubleCsrf } from "csrf-csrf";

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: { httpOnly: true, secure: true, sameSite: "lax" },
}));

const { doubleCsrfProtection, generateToken } = doubleCsrf({
  getSecret: () => process.env.CSRF_SECRET,
});

app.get("/form", (req, res) => res.json({ csrfToken: generateToken(req, res) }));
app.post("/transfer", doubleCsrfProtection, (req, res) => res.json({ ok: true }));

What is XSS and how do you defend against it?

Cross-Site Scripting injects malicious JavaScript into pages your users view, letting attackers steal cookies or tokens. Defenses are mostly about output, not Express middleware:

  • Escape/encode all user-controlled data when rendering HTML.
  • Set a Content-Security-Policy to restrict script sources.
  • Store auth tokens in HttpOnly cookies so injected scripts can’t read them.

Use helmet to set safe security headers in one line:

import helmet from "helmet";

app.use(helmet()); // sets CSP, X-Content-Type-Options, HSTS, and more

How does CORS work and how do you configure it?

CORS (Cross-Origin Resource Sharing) is a browser mechanism that controls which origins may call your API from client-side JS. The server signals permission via Access-Control-Allow-* headers. Use the cors package and an explicit allowlist — never reflect arbitrary origins or use * together with credentials.

import cors from "cors";

const allowed = ["https://app.devcraftly.com"];

app.use(cors({
  origin: (origin, cb) =>
    !origin || allowed.includes(origin) ? cb(null, true) : cb(new Error("Not allowed")),
  credentials: true,
}));

A successful preflight returns:

Output:

HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.devcraftly.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET,POST,PUT,DELETE

How do you implement authorization (not just authentication)?

Authentication answers “who are you?”; authorization answers “what may you do?”. Layer a role/permission check middleware after authentication:

const requireRole = (...roles) => (req, res, next) =>
  roles.includes(req.user?.role)
    ? next()
    : res.status(403).json({ error: "Forbidden" });

app.delete("/users/:id", authenticate, requireRole("admin"), deleteUser);

Best Practices

  • Hash passwords with bcrypt/Argon2 and a high cost factor; never use plain SHA or MD5.
  • Prefer HttpOnly, Secure, SameSite cookies; keep tokens out of localStorage.
  • Apply helmet and a strict Content-Security-Policy on every app.
  • Use short-lived access tokens with refresh tokens, and keep a revocation/denylist for JWTs.
  • Validate and sanitize all input; rate-limit auth endpoints (express-rate-limit) to slow brute force.
  • Enforce HTTPS, run npm audit, and keep dependencies patched.
  • Return generic auth errors to avoid user enumeration, and log security events for auditing.
Last updated June 14, 2026
Was this helpful?