// 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);
});
Topics
40
Advanced Node.js
Architecture & Design Patterns
Async Patterns
Authentication & Authorization
Authentication & Authorization (JWT, OAuth, Passport)
Background Jobs & Queues
Caching
Caching with Redis
Child Processes & Process Management
CLI Tools & Scripting with Node.js
Cloud & DevOps
Clustering & Worker Threads
Core Node.js Modules
Databases
Databases & ORMs (MongoDB/Mongoose, SQL/Sequelize)
Debugging & Diagnostics
Deployment & Process Managers (PM2)
Docker & Containerization for Node.js
Docker & Deployment
Email & Notifications
Environment Variables & Configuration
Error Handling
Event Loop & Non-blocking IO
Events & EventEmitter
Express & Middleware
File System & File Processing
File System (fs) Module
File Uploads & Media Processing
Git & Project Management
Global Objects & the process Object
GraphQL
GraphQL with Node.js
HTTP & HTTPS Modules
HTTP & Web Servers
Logging & Monitoring
Message Queues (RabbitMQ & Kafka)
Microservices Architecture with Node.js
Node.js Fundamentals & Runtime Architecture
Path & OS Modules
Performance Optimization & Profiling
Authentication & Authorization
15 questions found
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.
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.
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?
IntermediateIn 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.
Authentication & Authorization (JWT
OAuth
Passport);Caching with Redis
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.
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?
AdvancedA 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.
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?
AdvancedIn 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.
Authentication & Authorization (JWT
OAuth
Passport);Security
What is Passport.js, and how do its 'strategies' provide a consistent authentication abstraction across different methods?
IntermediatePassport.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.
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?
BeginnerPasswords 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.
Security;Databases & ORMs (MongoDB/Mongoose
SQL/Sequelize)
What is Role-Based Access Control (RBAC), and how would you implement it as Express middleware?
IntermediateRBAC 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.
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?
AdvancedMFA 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.
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?
IntermediateCSRF 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.
Security;Express & Middleware
Showing 1–10 of 15