Episode 3 — NodeJS MongoDB Backend Architecture / 3.14 — Authentication and Authorization
Interview Questions: Authentication & Authorization (Episode 3)
How to use this material (instructions)
- Read
3.14.athrough3.14.f. - Answer aloud, then compare below.
- Pair with
3.14-Exercise-Questions.md.
Q1. What is the difference between authentication and authorization?
Why interviewers ask: This is the most fundamental security concept; confusing the two signals a weak foundation.
Model answer:
Authentication verifies who you are -- proving identity through credentials like a password, token, or biometric. Authorization determines what you can do -- checking if the authenticated user has permission for a specific action or resource. Authentication always happens first; you cannot authorize someone without knowing their identity. In HTTP terms, 401 Unauthorized (misleadingly named) means not authenticated, while 403 Forbidden means authenticated but not authorized.
Q2. Why should passwords never be stored in plain text? What should you use instead?
Why interviewers ask: Tests understanding of basic security hygiene and database breach scenarios.
Model answer:
If the database is compromised (SQL injection, leaked backup, insider threat), plain-text passwords are immediately usable. Attackers could also try those passwords on other sites (credential stuffing). Instead, passwords should be hashed using a purpose-built algorithm like bcrypt or argon2. Hashing is one-way -- you cannot reverse it. bcrypt adds a unique salt per password and is intentionally slow (configurable cost factor of 10-12 rounds), making brute-force attacks computationally expensive. You verify by hashing the candidate password and comparing hashes, never by decrypting.
Q3. What is a salt in password hashing, and why is it important?
Why interviewers ask: Distinguishes candidates who understand why bcrypt is secure from those who just use it.
Model answer:
A salt is random data generated per-password before hashing. Without salts, identical passwords produce identical hashes, allowing rainbow table attacks (pre-computed lookup tables). With salts, even two users with the password "hello123" get completely different hashes. bcrypt generates and embeds the salt in the output string automatically ($2b$12$[22-char salt][31-char hash]), so you do not need to store it separately. This forces attackers to crack each password individually rather than all at once.
Q4. Explain the difference between session-based and token-based authentication.
Why interviewers ask: Tests architectural understanding; many projects require choosing between or combining both.
Model answer:
Session-based: After login, the server creates a session (stored in Redis/DB) and sends a session ID in a cookie. On each request, the server looks up the session. Pros: easy to invalidate (delete the session), simple logout. Cons: stateful -- requires shared session storage for multiple servers, cookie-based so harder for mobile and cross-domain.
Token-based (JWT): After login, the server signs a JWT containing user data and sends it to the client. The client sends it in the Authorization header on each request. The server verifies the signature -- no database lookup. Pros: stateless, scales easily, works across domains and on mobile. Cons: cannot easily revoke a token (must wait for expiry or maintain a blacklist).
Best practice for web apps: store JWTs in httpOnly cookies to get stateless verification with browser security protections.
Q5. What is a JWT and what are its three parts?
Why interviewers ask: JWT is the most common token format; interviewers want to know if you understand it beyond just calling jwt.sign().
Model answer:
JWT (JSON Web Token) is a compact, self-contained token defined by RFC 7519. It has three base64url-encoded parts separated by dots:
- Header: Algorithm and token type --
{ "alg": "HS256", "typ": "JWT" } - Payload: Claims (user data + metadata) --
{ "userId": "123", "role": "admin", "exp": 1701286400 } - Signature:
HMACSHA256(base64(header) + "." + base64(payload), secret)-- proves the token was not tampered with
Important: the payload is not encrypted -- anyone can decode it with atob(). The signature only ensures integrity, not confidentiality. Never put passwords or sensitive data in the payload.
Q6. How do access tokens and refresh tokens work together?
Why interviewers ask: Shows understanding of real-world token lifecycle management.
Model answer:
The access token is short-lived (15 minutes) and sent with every API request. The refresh token is long-lived (7 days) and stored securely (httpOnly cookie). When the access token expires, the client sends the refresh token to a /refresh endpoint, which verifies it and issues a new access token. This way, if an access token is stolen, damage is limited to 15 minutes. Refresh tokens should be stored in the database so they can be revoked (logout or compromise), and rotated on each use (old token invalidated, new one issued) to detect token theft.
Q7. Where should you store JWTs in the browser? Why?
Why interviewers ask: Probes understanding of XSS and CSRF attack vectors.
Model answer:
httpOnly cookie is the safest option. JavaScript cannot read httpOnly cookies, so XSS attacks cannot steal the token. Combine with secure: true (HTTPS only) and sameSite: 'lax' or 'strict' (CSRF protection).
localStorage is vulnerable to XSS -- any injected script can read localStorage.getItem('token') and exfiltrate it. sessionStorage is slightly better (cleared on tab close) but still XSS-vulnerable. In-memory (JS variable) is safe from XSS but lost on page refresh.
Best practice: refresh token in httpOnly cookie, access token in memory (with silent refresh on page load).
Q8. What is RBAC and how do you implement it in Express?
Why interviewers ask: Authorization implementation is a daily backend concern.
Model answer:
Role-Based Access Control (RBAC) assigns roles (user, moderator, admin) to users. Each role has defined permissions. In Express, implement RBAC as a middleware factory:
function authorize(...allowedRoles) {
return (req, res, next) => {
if (!allowedRoles.includes(req.user.role)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Usage: authenticate runs first, then authorize checks role
router.delete('/users/:id', authenticate, authorize('admin'), deleteUser);
The authenticate middleware must run before authorize because you need to know who the user is before checking what they can do. For finer control, you can use permission-based authorization where each role maps to specific permissions (user:read, post:delete).
Q9. How does Passport.js work? What is the strategy pattern?
Why interviewers ask: Passport is the dominant auth library in Node.js; understanding its architecture is expected.
Model answer:
Passport uses the strategy pattern: each authentication method (local, Google OAuth, JWT, GitHub) is a separate, pluggable strategy. You configure a strategy with a verify callback that receives credentials and calls done(null, user) on success or done(null, false) on failure. For session-based auth, serializeUser stores the user ID in the session, and deserializeUser retrieves the full user on subsequent requests.
Key methods: passport.authenticate('local') triggers the local strategy; passport.authenticate('google') redirects to Google OAuth. For JWT-based APIs, use passport-jwt with { session: false } so no serialize/deserialize is needed -- the strategy just verifies the token and sets req.user.
Q10. A user's JWT says role: "admin" but you demoted them to "user" in the database. What happens?
Why interviewers ask: Tests understanding of stateless tokens and their limitations.
Model answer:
The JWT still says role: "admin" because the payload is fixed at creation time. The token remains valid until it expires. This is a fundamental trade-off of stateless tokens: stale data. Mitigation strategies:
- Short-lived access tokens (15 min) -- stale data window is small
- Check the database in auth middleware for critical operations (costs a DB call)
- Token blacklist in Redis -- blacklist the old token when role changes
- Force re-authentication when role changes (delete refresh token)
This is why session-based auth can be simpler for apps where instant permission changes matter.
Q11. How do you handle Google OAuth in a Node.js app?
Why interviewers ask: Social login is a standard feature; tests real-world implementation knowledge.
Model answer:
- Register the app in Google Cloud Console to get a Client ID and Secret.
- Install
passport-google-oauth20and configure the strategy withclientID,clientSecret,callbackURL, and a verify callback. - Create two routes:
GET /auth/googletriggers the redirect to Google's consent screen;GET /auth/google/callbackreceives the authorization code. - In the verify callback, Passport exchanges the code for an access token and profile data (name, email, photo). Your callback should: (a) check if a user with that Google ID exists, (b) if not, check if the email matches an existing local account (for account linking), (c) if neither, create a new user.
- For session-based apps, Passport serializes the user into the session. For SPAs, issue a JWT in the callback and redirect to the frontend with it.
Q12. What security headers and practices should surround your auth implementation?
Why interviewers ask: Auth code alone is not enough; defense in depth matters.
Model answer:
Beyond the auth code itself: use HTTPS everywhere (never transmit tokens over HTTP). Set cookie flags: httpOnly (prevent XSS token theft), secure (HTTPS only), sameSite (prevent CSRF). Implement rate limiting on login endpoints (prevent brute force). Use helmet middleware for security headers. Hash passwords with bcrypt (12 rounds). Store secrets in environment variables, never in code. Validate and sanitize all input. Return generic error messages for login failures ("Invalid credentials" not "User not found") to prevent user enumeration. Add CORS configuration to restrict which domains can call your API.
Quick-fire
| # | Question | One-line |
|---|---|---|
| 1 | 401 vs 403 | 401 = not authenticated; 403 = not authorized |
| 2 | bcrypt salt rounds for production | 10--12 |
| 3 | Can you reverse a bcrypt hash? | No -- one-way |
| 4 | JWT payload encrypted? | No -- base64-encoded, readable by anyone |
| 5 | jwt.verify vs jwt.decode | verify checks signature + expiry; decode does not |
| 6 | Session invalidation | Delete from store |
| 7 | JWT invalidation | Blacklist or wait for expiry |
| 8 | Where to store refresh token | httpOnly cookie |
| 9 | Passport strategy for Google | passport-google-oauth20 |
| 10 | isModified('password') purpose | Prevent re-hashing an already-hashed password on save |
<- Back to 3.14 -- README