Authentication & Authorization

15 questions found

What is the difference between authentication and authorization in a Node.js application?

Beginner
Authentication verifies who a user is -- confirming their identity, typically through a username/password, a token, or a third-party identity provider. Authorization determines what an already-authenticated user is allowed to do -- which resources they can access and which actions they can perform. A request must always be authenticated before it's meaningful to authorize it, but a user can be successfully authenticated and still be denied authorization for a specific action.
// Authentication: verifying identity
app.post('/login', async (req, res) => {
  const user = await verifyCredentials(req.body.username, req.body.password);
  if (!user) return res.status(401).json({ error: 'Invalid credentials' });
  req.session.userId = user.id;
});

// Authorization: checking permission for an already-authenticated user
app.delete('/posts/:id', requireAuth, (req, res) => {
  if (req.user.role !== 'admin') return res.status(403).json({ error: 'Forbidden' });
  deletePost(req.params.id);
});
Real-world example A blogging platform lets any logged-in (authenticated) user comment on posts, but only lets users with the 'editor' or 'admin' role (authorization) actually delete or edit other users' posts, checking these as two clearly separate steps in the request pipeline.

Common follow-ups: What HTTP status codes are conventionally used to distinguish an authentication failure from an authorization failure?;How does session-based authentication differ architecturally from token-based authentication?

Authentication & Authorization (JWT OAuth Passport);Security

How does session-based authentication work in a Node.js/Express application, and what does the session store need to handle?

Intermediate
In session-based authentication, after a successful login the server creates a session record (containing the user's identity and any relevant data) stored server-side, and sends the client only an opaque session ID via a cookie -- each subsequent request includes that cookie, and the server looks up the corresponding session data before processing the request. The session store (in-memory for development, but Redis or a database in production for scalability across multiple server instances) needs to handle expiration, and in-memory stores don't work correctly once an application is scaled horizontally across multiple processes without a shared store.
const session = require('express-session');
const RedisStore = require('connect-redis').default;

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  cookie: { secure: true, httpOnly: true, maxAge: 86400000 },
}));

app.post('/login', (req, res) => {
  req.session.userId = user.id; // stored server-side, keyed by the session ID cookie
  res.json({ success: true });
});
Real-world example A service running three load-balanced instances of the same Express app switches its session store from the default in-memory store to Redis, since a user's session previously only 'existed' on whichever specific server instance happened to handle their login request, causing random logouts when subsequent requests hit a different instance.

Common follow-ups: Why is httpOnly on the session cookie an important security setting?;What specifically breaks about in-memory session storage the moment an app is scaled to multiple processes or servers?

Authentication & Authorization (JWT OAuth Passport);Caching with Redis

What is a JSON Web Token (JWT), and what are its three parts?

Intermediate
A JWT is a compact, self-contained, digitally signed token used to represent claims (like a user's identity and permissions) that can be verified without needing a server-side lookup, unlike a session ID. It consists of three base64url-encoded, dot-separated parts: a header (specifying the signing algorithm and token type), a payload (the actual claims, like userId and role), and a signature (computed over the header and payload using a secret or private key, allowing the server to verify the token hasn't been tampered with).
const jwt = require('jsonwebtoken');

const token = jwt.sign({ userId: user.id, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' });

// Verifying an incoming token
try {
  const decoded = jwt.verify(token, process.env.JWT_SECRET);
  req.user = decoded;
} catch (err) {
  return res.status(401).json({ error: 'Invalid or expired token' });
}
Real-world example A mobile app authenticates once against an API and stores the returned JWT locally, attaching it as a Bearer token on every subsequent request, letting the API verify the user's identity statelessly on each server instance without needing a shared session store at all.

Common follow-ups: Why is it critical to never store sensitive data like a password inside a JWT's payload, given the payload is only encoded, not encrypted?;How do you handle revoking a JWT before its natural expiration, given it's normally self-contained and stateless?

Authentication & Authorization (JWT OAuth Passport);Security

How do you implement JWT refresh token rotation in a Node.js API, and why is it more secure than a single long-lived token?

Advanced
A single long-lived JWT, if stolen, remains valid and dangerous for its entire lifetime. Refresh token rotation instead issues a short-lived access token (minutes) alongside a longer-lived refresh token (days/weeks) stored securely (often an httpOnly cookie); when the access token expires, the client uses the refresh token to obtain a new access token AND a new refresh token, invalidating the old refresh token -- if a stolen refresh token is ever used after the legitimate client has already rotated it, the reuse can be detected and the entire token family revoked.
app.post('/refresh', async (req, res) => {
  const oldRefreshToken = req.cookies.refreshToken;
  const stored = await refreshTokenStore.find(oldRefreshToken);
  if (!stored || stored.used) {
    await refreshTokenStore.revokeFamily(stored?.familyId); // detected reuse: revoke everything
    return res.status(401).json({ error: 'Token reuse detected' });
  }
  await refreshTokenStore.markUsed(oldRefreshToken);
  const newAccessToken = jwt.sign({ userId: stored.userId }, JWT_SECRET, { expiresIn: '15m' });
  const newRefreshToken = await refreshTokenStore.create(stored.userId, stored.familyId);
  res.cookie('refreshToken', newRefreshToken, { httpOnly: true, secure: true }).json({ accessToken: newAccessToken });
});
Real-world example A banking API implements refresh token rotation with reuse detection, so that if an attacker manages to steal a refresh token and use it after the legitimate user's client has already rotated past it, the system automatically revokes every token in that session's family, immediately locking out the attacker's stolen credential.

Common follow-ups: Where should the refresh token be stored on the client to minimize XSS exposure risk?;What's the tradeoff of shorter versus longer access token lifetimes in this scheme?

Authentication & Authorization (JWT OAuth Passport);Security

How does the OAuth 2.0 Authorization Code flow work, and what role does Node.js typically play as the client or the resource server?

Advanced
In the Authorization Code flow, a user is redirected to the authorization server (like Google) to log in and consent; the authorization server redirects back to the application with a temporary authorization code, which the application's backend (never the browser, to keep it confidential) exchanges directly with the authorization server for an access token (and often a refresh token) -- this two-step exchange, with the code exchange happening server-to-server, is what prevents the access token from ever being exposed in browser history or a redirect URL. A Node.js backend commonly plays the role of the 'client' performing this exchange, or the 'resource server' validating access tokens presented by other clients.
// Step 2: exchanging the authorization code for tokens (server-to-server)
const response = await fetch('https://oauth-provider.com/token', {
  method: 'POST',
  body: new URLSearchParams({
    grant_type: 'authorization_code',
    code: req.query.code,
    redirect_uri: REDIRECT_URI,
    client_id: CLIENT_ID,
    client_secret: CLIENT_SECRET, // kept only on the server, never sent to the browser
  }),
});
const { access_token } = await response.json();
Real-world example A Node.js application implements 'Sign in with Google' using Passport's OAuth strategy, where the backend handles the authorization-code exchange with Google's token endpoint directly, ensuring the application's client secret is never exposed to the browser at any point in the flow.

Common follow-ups: Why is the Authorization Code flow considered more secure than the older Implicit flow for a server-side application?;What is PKCE, and why is it now recommended even for confidential clients?

Authentication & Authorization (JWT OAuth Passport);Security

What is Passport.js, and how do its 'strategies' provide a consistent authentication abstraction across different methods?

Intermediate
Passport.js is a widely used Express-compatible authentication middleware built around the concept of 'strategies' -- pluggable modules each implementing one specific authentication method (local username/password, Google OAuth, JWT, SAML, and hundreds of others) behind a consistent interface, so an application can support multiple authentication methods side by side, or switch between them, with minimal changes to route-handling code.
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;

passport.use(new LocalStrategy(async (username, password, done) => {
  const user = await User.findOne({ username });
  if (!user || !(await user.comparePassword(password))) return done(null, false);
  return done(null, user);
}));

app.post('/login', passport.authenticate('local'), (req, res) => res.json({ success: true }));
Real-world example An application supporting both traditional username/password login and 'Sign in with GitHub' registers both a LocalStrategy and a GitHubStrategy with Passport, letting both authentication paths converge on the same req.user object and the same downstream authorization logic.

Common follow-ups: How does Passport's session serialization (serializeUser/deserializeUser) work to keep the session payload small?;What's a scenario where implementing authentication manually might be preferable to depending on Passport?

Authentication & Authorization (JWT OAuth Passport);Express & Middleware

How should passwords be securely stored in a Node.js application's database, and why is bcrypt commonly used?

Beginner
Passwords should never be stored in plain text or with a fast, general-purpose hash like SHA-256 alone -- they should be hashed with a purpose-built, deliberately slow password-hashing algorithm like bcrypt (or argon2/scrypt), which incorporates a random per-password salt automatically and can be tuned to be computationally expensive enough to make brute-force and rainbow-table attacks impractical, even if the password database is ever leaked.
const bcrypt = require('bcrypt');

// Hashing during registration
const hashedPassword = await bcrypt.hash(plainTextPassword, 12); // 12 = cost factor
await User.create({ username, password: hashedPassword });

// Verifying during login
const isMatch = await bcrypt.compare(submittedPassword, user.password);
Real-world example A company's user database is leaked in a breach, but because passwords were hashed with bcrypt at a cost factor of 12 rather than stored as plain SHA-256 hashes, attackers are unable to feasibly crack the vast majority of passwords even with significant computing resources, buying users critical time to change their passwords.

Common follow-ups: What is a 'cost factor' in bcrypt, and how do you choose an appropriate value that balances security and login-request latency?;Why is a per-password random salt important even when using a strong hashing algorithm?

Security;Databases & ORMs (MongoDB/Mongoose SQL/Sequelize)

What is Role-Based Access Control (RBAC), and how would you implement it as Express middleware?

Intermediate
RBAC assigns each user one or more roles (like 'admin', 'editor', 'viewer'), and access to specific resources or actions is granted based on the role rather than checking individual permissions per user -- this simplifies permission management considerably when many users share the same access needs, since granting or revoking access to a whole category of actions is done once at the role level rather than per user.
function requireRole(...allowedRoles) {
  return (req, res, next) => {
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Insufficient permissions' });
    }
    next();
  };
}

app.delete('/users/:id', requireAuth, requireRole('admin'), deleteUserHandler);
app.put('/posts/:id', requireAuth, requireRole('admin', 'editor'), updatePostHandler);
Real-world example A content-management API restricts the DELETE /users/:id endpoint to only the 'admin' role while allowing both 'admin' and 'editor' roles to update posts, implemented as a single reusable requireRole() middleware parameterized differently per route rather than duplicating permission-check logic in each handler.

Common follow-ups: How does RBAC compare to a more granular permission-based (or ABAC, Attribute-Based Access Control) system when roles alone aren't expressive enough?;How would you handle a user who needs temporary elevated access for a specific task?

Express & Middleware;Security

What is Multi-Factor Authentication (MFA), and how would you implement Time-based One-Time Password (TOTP) support in a Node.js app?

Advanced
MFA requires a user to prove their identity via two or more independent factors (something they know, like a password; something they have, like a phone; something they are, like a fingerprint) -- TOTP is a common 'something you have' factor that generates a six-digit code changing every 30 seconds, derived from a shared secret and the current time, verifiable without any network round-trip since both the server and an authenticator app (like Google Authenticator) compute the same code independently from the same secret and clock.
const speakeasy = require('speakeasy');

// Generating a secret during MFA setup
const secret = speakeasy.generateSecret({ name: 'MyApp' });
await User.update(userId, { totpSecret: secret.base32 });

// Verifying a code at login
const isValid = speakeasy.totp.verify({
  secret: user.totpSecret,
  encoding: 'base32',
  token: req.body.code,
  window: 1, // allows for slight clock drift
});
Real-world example A financial application requires users to enable TOTP-based MFA before accessing sensitive account settings, storing each user's TOTP secret encrypted at rest and verifying submitted six-digit codes with a small time-drift window to account for minor clock differences between the server and the user's phone.

Common follow-ups: How should the TOTP secret itself be protected in the database, given it's effectively as sensitive as a password?;What backup mechanism should be provided for users who lose access to their authenticator device?

Security;Databases & ORMs (MongoDB/Mongoose SQL/Sequelize)

What is CSRF (Cross-Site Request Forgery), and how do you protect a Node.js session-based application against it?

Intermediate
CSRF tricks a logged-in user's browser into unknowingly submitting a malicious request to a site where they're already authenticated (since the browser automatically attaches the relevant session cookie), potentially performing an unwanted action like transferring funds or changing an email address. Protection typically combines the SameSite cookie attribute (set to 'Strict' or 'Lax', preventing the cookie from being sent on most cross-site requests) with a CSRF token -- a unique, unpredictable value embedded in forms and verified server-side, which an attacker's forged request can't know or supply.
app.use(cookieParser());
const csrfProtection = csrf({ cookie: true });

app.get('/form', csrfProtection, (req, res) => {
  res.render('form', { csrfToken: req.csrfToken() });
});

app.post('/transfer', csrfProtection, (req, res) => {
  // request automatically rejected if the csrf token doesn't match
  processTransfer(req.body);
});
Real-world example A banking application sets its session cookie's SameSite attribute to 'Strict' and additionally requires a CSRF token on all state-changing (POST/PUT/DELETE) requests, so that even if a user visits a malicious site while logged in, that site cannot successfully trigger a funds transfer on the user's behalf.

Common follow-ups: Why is CSRF primarily a concern for cookie/session-based authentication and largely irrelevant for token-based APIs that don't rely on cookies?;What's the difference between SameSite=Strict and SameSite=Lax in terms of what cross-site requests they actually block?

Security;Express & Middleware

Showing 1–10 of 15