"How would you implement authentication?" is a common backend interview question. A strong answer starts with the threat model, client type, trust boundaries, recovery requirements and authorization model rather than defaulting to JWT or a particular Node.js package.
This guide covers the essential authentication questions you'll face in Node.js backend interviews, from basic concepts to advanced security patterns.
Table of Contents
- Authentication Fundamentals Questions
- Session-Based Authentication Questions
- JWT Authentication Questions
- OAuth 2.0 Questions
- Password Security Questions
- Multi-Factor Authentication Questions
- Security Best Practices Questions
Authentication Fundamentals Questions
These foundational questions test your understanding of core authentication concepts.
What is the difference between authentication and authorization?
Authentication establishes or verifies a principal. Authorization evaluates that subject, action, resource, tenant and relevant state. Public access can be authorized anonymously, so authentication does not literally precede every authorization decision. For protected HTTP operations, 401 means valid authentication credentials are missing or invalid; 403 means the server understood the request but refuses it, commonly because an authenticated principal lacks permission.
flowchart TB
subgraph authn["Authentication: WHO are you?"]
A1["Verify identity"]
A2["Check credentials<br/>(password, token, biometric)"]
A3["Result: This is user #123"]
end
subgraph authz["Authorization: WHAT can you do?"]
B1["Check permissions"]
B2["Evaluate roles/policies"]
B3["Result: User #123 can<br/>edit this resource"]
end
authn --> authz// Express middleware example
const authenticate = async (req, res, next) => {
// WHO is this?
const token = req.cookies.token;
const user = await verifyToken(token);
if (!user) return res.status(401).json({ error: 'Not authenticated' });
req.user = user;
next();
};
const authorize = (action, loadResource) => async (req, res, next) => {
const resource = await loadResource(req.params.id);
// Evaluate action, resource, tenant, ownership and current policy—not role alone.
if (!await policy.allows(req.user, action, resource)) {
return res.status(403).json({ error: 'Not authorized' });
}
req.resource = resource;
next();
};
// Usage
app.delete('/users/:id',
authenticate, // Must be logged in
authorize('delete', loadUserResource),
deleteUser
);How would you implement authentication in a Node.js application?
Start by deciding whether the application should own credentials at all. A maintained identity provider can supply passkeys/MFA, recovery, federation and security operations that are easy to implement badly. A same-origin browser app often benefits from an opaque, rotating session cookie or a backend-for-frontend (BFF). Native and third-party clients commonly use OAuth authorization code with PKCE; user login additionally needs OpenID Connect. A service-to-service API may accept an opaque access token or a tightly validated JWT.
A maintained Node.js framework integration can help, but no middleware automatically supplies safe recovery, account linking, protocol validation, session operations and authorization. Verify its security model, maintenance and configuration rather than treating a library or managed provider as proof of correctness.
When should you use sessions versus JWT?
Sessions normally send an opaque identifier while the server retains authoritative state. A JWT is a token format, not an authentication architecture: its claims may be signed (JWS), encrypted (JWE), both through nesting, or even unsecured where a profile permits it. Production security profiles require cryptographic protection.
Choose according to trust boundaries and lifecycle. Shared session storage can scale horizontally; a JWT deployment often still needs keys, user status, consent, revocation or replay state. Cookies and tokens can both cross domains when deliberately scoped and transported, so neither row is an architectural law.
| Requirement | Session | JWT |
|---|---|---|
| Server-rendered web app | Yes | Possible |
| Native/mobile API | OAuth session or opaque token | Profiled access token |
| Independently operated recipients | Possible, needs lookup | Useful with strict validation |
| Immediate revocation | Central lookup is direct | Introspection, denylist or short lifetime |
| Horizontal scaling | Shared/partitioned store | Shared keys plus any lifecycle state |
Session-Based Authentication Questions
Sessions are the traditional approach, still widely used for server-rendered web apps.
How does session-based authentication work?
After login, the server creates a high-entropy session identifier and stores authoritative state in a server-side store. The browser receives only the identifier in a cookie and sends it on matching requests. The application resolves it, checks expiry and revocation, then loads current authorization context.
Central state makes revocation direct, provided every node consults coherent state and no downstream credential outlives the session.
1. User submits credentials
2. Server validates, rotates the pre-login ID, creates server-side state
3. Server sends a high-entropy ID in a protected cookie
4. Browser sends cookie with every request
5. Server looks up session, attaches user to request
How do you implement session authentication in Express?
express-session can manage the cookie and lifecycle, but production readiness also depends on a supported shared store, TLS/proxy configuration, error handling, expiry alignment, regeneration, revocation and CSRF protection. HttpOnly blocks direct JavaScript reads and Secure limits transport to HTTPS. SameSite is useful defense in depth, not a universal replacement for CSRF tokens or strict Origin validation.
const express = require('express');
const session = require('express-session');
app.use(session({
store: productionSessionStore, // Connected and monitored elsewhere
name: '__Host-session',
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'lax',
path: '/',
maxAge: sessionLifetimeMs
}
}));
// Add maintained CSRF protection for state-changing cookie-authenticated routes.
// Login
app.post('/login', async (req, res, next) => {
const { email, password } = req.body;
// Includes a precomputed dummy hash path so unknown accounts do comparable work.
const user = await verifyPasswordWithoutEnumeration({ email, password });
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
// Regenerate session to prevent fixation
req.session.regenerate((err) => {
if (err) return next(err);
req.session.userId = user.id;
req.session.save((saveError) => {
if (saveError) return next(saveError);
res.json({ message: 'Logged in' });
});
});
});
// Logout
app.post('/logout', (req, res, next) => {
req.session.destroy((err) => {
if (err) return next(err);
res.clearCookie('__Host-session', { secure: true, httpOnly: true, sameSite: 'lax', path: '/' });
res.json({ message: 'Logged out' });
});
});
// Auth middleware
const requireAuth = (req, res, next) => {
if (!req.session.userId) {
return res.status(401).json({ error: 'Not authenticated' });
}
next();
};What are the pros and cons of session-based authentication?
Opaque sessions keep claims and lifecycle decisions server-side, make per-session revocation straightforward and send a relatively small credential. Their store must be available, protected, expired and scaled. Cookie transport is automatic, so state-changing requests need CSRF defenses. Cross-origin deployment is possible but adds deliberate cookie, CORS, CSRF and browser-policy design.
| Pros | Cons |
|---|---|
| Immediate revocation (delete session) | Requires server-side storage |
| Small opaque browser credential | Store availability and cleanup matter |
| Server controls authoritative state | Cross-origin flows add browser constraints |
| Straightforward lifecycle model | Cookie transport needs CSRF protection |
JWT Authentication Questions
JWT is a standardized claims format used by some identity and authorization profiles. It is not automatically the right format for an API or microservice.
What is a JWT and what does its structure look like?
A JWT is a compact claims format defined by RFC 7519. The familiar three-part form is a JWS carrying a protected header, claim set and signature. Base64url encoding is not encryption: anyone holding such a token can normally read its claims. JWT can also be encrypted as JWE or nested, while an unsecured JWT exists in the base standard but should not be accepted by security profiles.
Signature verification proves integrity under a selected key; it does not by itself prove that the token is acceptable here. The recipient must pin allowed algorithms and trusted key sources, then validate issuer, audience, token type/use, lifetime and application claims. It may still consult state for subject status, authorization, consent or revocation.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4iLCJpYXQiOjE1MTYyMzkwMjJ9.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
[Header].[Payload].[Signature]
// Header (algorithm + type)
{
"alg": "HS256",
"typ": "JWT"
}
// Payload (claims)
{
"sub": "user123", // Subject (user ID)
"name": "John Doe",
"role": "admin",
"iat": 1516239022, // Issued at
"exp": 1516242622 // Expiration
}
// Conceptual JWS signature input (the concrete algorithm is profile-specific)
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)How do you implement JWT authentication with refresh tokens?
Access-token and refresh-token lifetimes are risk decisions, not universal 15-minute and 7-day constants. A refresh token is optional and represents a valuable grant. RFC 9700 requires public clients that receive one to use sender constraint or rotation with reuse detection; the server must retain the token-family relationship and revoke the active family when an invalidated token is replayed.
The example below deliberately uses an opaque refresh token. rotateRefreshTokenAtomically must hash tokens at rest, verify client/grant binding and expiry, consume the presented token exactly once, create its successor and revoke the family on reuse. Merely signing a fresh JWT with the same claims is not rotation.
const jwt = require('jsonwebtoken');
const crypto = require('node:crypto');
const issueAccessToken = (subject) => jwt.sign(
{ sub: subject, token_use: 'access' },
accessTokenPrivateKey,
{
algorithm: 'RS256',
issuer: 'https://identity.example.com',
audience: 'https://api.example.com',
expiresIn: accessTokenLifetime,
jwtid: crypto.randomUUID(),
header: { typ: 'at+jwt', kid: activeKeyId }
}
);
app.post('/refresh', verifyCsrf, async (req, res, next) => {
try {
const presented = req.cookies['__Host-refresh'];
if (!presented) return res.status(401).json({ error: 'Invalid grant' });
// One transaction: consume once, detect replay, revoke family on reuse,
// and return a random successor whose hash (not plaintext) is stored.
const rotated = await rotateRefreshTokenAtomically(presented, {
clientId: expectedClientId
});
res.cookie('__Host-refresh', rotated.plaintextToken, {
httpOnly: true,
secure: true,
sameSite: 'lax',
path: '/',
maxAge: rotated.remainingLifetimeMs
});
res.json({ accessToken: issueAccessToken(rotated.subject) });
} catch (error) {
next(error); // Map expected grant failures to one generic 401 response.
}
});
// Resource-server validation: the public key comes from trusted issuer metadata.
const authenticateJWT = (req, res, next) => {
const match = req.headers.authorization?.match(/^Bearer ([^\s]+)$/);
if (!match) return res.status(401).json({ error: 'Invalid token' });
try {
const verified = jwt.verify(match[1], trustedAccessTokenPublicKey, {
algorithms: ['RS256'],
issuer: 'https://identity.example.com',
audience: 'https://api.example.com',
complete: true
});
if (verified.header.typ !== 'at+jwt' || verified.payload.token_use !== 'access') {
throw new Error('Wrong token type');
}
req.user = verified.payload;
next();
} catch {
return res.status(401).json({ error: 'Invalid token' });
}
};How do you handle logout with JWT?
JWT does not prohibit revocation; offline validation simply cannot observe changed server state. On logout, revoke the current session or authorization grant and refresh-token family, then clear the browser credential with matching cookie attributes. Decide separately whether access tokens may remain valid for their bounded lifetime or whether sensitive APIs must use introspection, a denylist keyed by jti, sender-constrained tokens or another live-state check. “Logout everywhere” and “logout this device” need different identifiers.
app.post('/logout', verifyCsrf, async (req, res, next) => {
await revokeCurrentGrant(req.cookies['__Host-refresh']);
res.clearCookie('__Host-refresh', {
secure: true, httpOnly: true, sameSite: 'lax', path: '/'
});
res.json({ message: 'Logged out' });
});What are common JWT security pitfalls?
Common failures include accepting an attacker-selected algorithm or key, validating a signature but not iss, aud, time and token type, sharing one validation policy across access tokens and ID tokens, trusting arbitrary jku/jwk/kid values, leaking bearer tokens, putting sensitive data in readable claims and operating without key rotation. RFC 8725 requires mutually exclusive validation rules for different JWT kinds.
// BAD: Storing in localStorage (XSS vulnerable)
localStorage.setItem('token', accessToken);
// BAD: signature-only validation with a caller-selected key or algorithm
jwt.verify(token, keyFromTokenHeader);
// BETTER: separate, explicit policy for this access-token profile
const verified = jwt.verify(token, keyFromTrustedIssuerMetadata, {
algorithms: ['RS256'],
issuer: expectedIssuer,
audience: expectedApiAudience,
complete: true
});
if (verified.header.typ !== 'at+jwt' || verified.payload.token_use !== 'access') {
throw new Error('Wrong token type');
}What are the pros and cons of JWT authentication?
Signed JWTs let a recipient validate an issuer's protected claims without a per-request lookup and can interoperate across separately operated services. Costs include key distribution and rotation, larger credentials, claim staleness, strict profile validation and a deliberate replay/revocation model. Sensitive claims remain readable unless encryption is used, and encryption still does not make a stolen bearer token safe.
| Pros | Cons |
|---|---|
| Offline integrity validation | Claims can become stale |
| Standard claims and profiles | Validation policy is easy to misconfigure |
| Useful across trust boundaries | Key lifecycle and replay need design |
| Avoids some request-time lookups | Usually larger; payload is normally readable |
What happens if a JWT secret is compromised?
A compromised signing key is an incident: stop issuance, identify affected token types and audiences, activate a prepared replacement, remove trust in the old key as quickly as the compatibility window permits, revoke grants/sessions where needed, investigate use and notify according to the response plan. Tokens signed with the removed key fail; not every user must necessarily reauthenticate if a separate session or refresh grant remains trustworthy.
Asymmetric signing lets resource servers receive only public verification keys, reducing signing authority spread, but the private key and publication pipeline still require hardened storage, access control, rotation, kid handling and monitoring. Separate keys and mutually exclusive validation rules can limit blast radius only when their operational trust boundaries are genuinely separate.
OAuth 2.0 Questions
OAuth 2.0 delegates access to protected resources. OpenID Connect (OIDC), not bare OAuth, defines federated user authentication.
How does OAuth 2.0 work?
In the authorization code flow, a client creates a transaction-bound state and PKCE verifier, redirects the browser to a trusted authorization endpoint and receives a one-time code at an exactly registered redirect URI. It validates the response and exchanges the code plus verifier at the token endpoint. The access token is for the resource server; an OIDC ID token is evidence for the client and must not be sent as an API access token.
RFC 9700 requires PKCE for public clients and recommends it for confidential clients. Implementations also need issuer/mix-up protection, exact redirect matching, least-privilege scopes, trusted metadata, TLS and secure token handling. Consent may be part of a flow but is not guaranteed on every request.
sequenceDiagram
participant U as User Browser
participant A as Your App (Backend)
participant G as Google Auth Server
U->>A: 1. Click "Login with Google"
A->>G: 2. Authorization request + state + PKCE challenge
G->>U: 3. Authenticate and, when needed, ask consent
U->>A: 4. Exact redirect URI + code + state
A->>A: 5. Validate transaction and issuer
A->>G: 6. Code + PKCE verifier
G->>A: 7. Access token + OIDC ID token
A->>A: 8. Validate ID token and bind provider subject
A->>U: 9. Rotate/create local sessionHow do you implement OAuth with Passport.js?
Passport.js strategies can integrate a provider, but package choice does not remove protocol verification duties. Confirm that the maintained strategy/library supports state, PKCE where required, exact redirect URIs, issuer and OIDC nonce/ID-token validation. Bind the account to the stable (issuer, subject) pair; do not merge accounts solely because an untrusted or unverified email string matches. The abbreviated example omits production error pages, account-linking confirmation and token lifecycle by design.
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: 'https://app.example.com/auth/google/callback',
state: true
},
async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user
let user = await User.findOne({ googleId: profile.id });
if (!user) {
user = await User.create({
googleId: profile.id, // Provider subject; store issuer alongside it.
email: verifiedProviderEmail(profile),
name: profile.displayName,
avatar: profile.photos[0]?.value
});
}
return done(null, user);
} catch (error) {
return done(error, null);
}
}
));
// Serialize user to session
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await User.findById(id);
done(null, user);
});
// Routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
res.redirect('/dashboard');
}
);What are the different OAuth 2.0 grant types?
Choose a grant by who acts, what credentials the client can protect and which current profile applies. Authorization Code is the interactive redirect flow; public clients must use PKCE, and confidential clients should use it too. Client Credentials is for a confidential client acting on its own behalf, not impersonating a user. Device Authorization Grant covers input-constrained devices when supported.
RFC 9700 says authorization servers must not use the implicit grant and must not use the Resource Owner Password Credentials grant. Do not describe these as merely lower-security options for new systems.
| Grant | Appropriate context | Key requirement |
|---|---|---|
| Authorization Code + PKCE | Browser, native and web clients | Bind code to transaction and verifier |
| Client Credentials | Confidential service acting as itself | Authenticate client; narrow audience/scope |
| Device Authorization | Input-constrained device | User code verification and polling controls |
| Implicit | Legacy only | Do not enable for new systems |
| Resource Owner Password | Legacy only | Do not enable; migrate away |
Password Security Questions
Password handling is heavily tested in interviews.
How do you securely store passwords?
Store passwords with a salted, adaptive password-hashing function. OWASP currently prefers Argon2id; use scrypt when it is unavailable, bcrypt mainly for legacy compatibility, or the approved PBKDF2 profile where FIPS requirements apply. Calibrate memory, CPU and parallelism on production-class hardware under expected concurrency, record the algorithm and parameters with each hash, and rehash after successful login when policy changes. A salt prevents identical hashes and precomputed tables; it does not stop offline guessing. A separately protected pepper can add defense in depth.
Bcrypt has a 72-byte input limit in common implementations, so silent truncation must be prevented. There is no universal rounds value or response-time target: benchmark it, protect the service from resource exhaustion and revisit parameters.
const argon2 = require('argon2');
const hashPassword = (password) => argon2.hash(password, {
type: argon2.argon2id,
...benchmarkedProductionParameters
});
const verifyPassword = (password, encodedHash) =>
argon2.verify(encodedHash, password);How do you implement password validation?
NIST SP 800-63B-4 requires at least 15 characters when a password is the only factor and permits a minimum of eight when it is used only within MFA. It says to allow a maximum of at least 64 characters, accept spaces and Unicode, reject known common/expected/compromised values, allow password managers and paste, and not impose character-composition rules or periodic rotation without evidence of compromise. Normalize Unicode consistently before hashing and document any sensible resource-exhaustion ceiling.
const validatePassword = async (password, { isSingleFactor }) => {
const errors = [];
const normalized = password.normalize('NFC');
const minimum = isSingleFactor ? 15 : 8;
if ([...normalized].length < minimum) errors.push(`Use at least ${minimum} characters`);
if (Buffer.byteLength(normalized, 'utf8') > serviceSafetyLimitBytes) errors.push('Password is too long');
if (await compromisedPasswordBlocklist.has(normalized)) errors.push('Choose a password not found in common or breached-password data');
return errors;
};How do you implement secure password reset?
Password reset is an authentication path and needs the same threat model as login. Return the same outward response and similar timing for existing and unknown accounts. Generate a high-entropy, single-use token, store only a keyed hash, bind it to the account and purpose, set a risk-based short expiry, rate-limit requests and build links from a configured trusted origin rather than the request Host header. Consume it atomically, invalidate competing reset tokens and decide which sessions/grants to revoke. Notify the user after a change without sending the new password.
const crypto = require('crypto');
const generateResetToken = async (user) => {
const token = crypto.randomBytes(32).toString('hex');
const hashedToken = keyedTokenHash(token);
user.resetToken = hashedToken;
user.resetTokenExpiry = Date.now() + resetLifetimeMs;
await user.save();
return token; // Send this to user, store hashed version
};
const verifyResetToken = async (token) => {
// One transaction consumes the token once and clears every competing token.
return consumeValidResetTokenAtomically(keyedTokenHash(token), Date.now());
};Multi-Factor Authentication Questions
MFA adds critical security for sensitive applications.
How do you implement TOTP-based MFA?
TOTP is a shared-secret possession factor defined by RFC 6238. It is widely supported but remains phishable; for higher-risk systems, prefer phishing-resistant WebAuthn/passkeys when the threat model and recovery design support them. Require recent authentication before enrollment, create an expiring pending enrollment, present the secret once over a protected channel, encrypt it at rest, confirm a code before activation and issue separately protected recovery codes. Rate-limit verification, keep clock tolerance narrow and reject replay of an already accepted time step.
app.post('/mfa/setup', requireRecentAuthentication, async (req, res) => {
const enrollment = await createPendingTotpEnrollment(req.user.id, {
expiresInMs: enrollmentLifetimeMs,
encryptSecretWith: mfaEncryptionKey
});
// The QR encodes the secret; show it only in this protected enrollment flow.
res.json({ qrCode: await renderQr(enrollment.otpauthUri) });
});
app.post('/mfa/verify', requireRecentAuthentication, mfaRateLimit, async (req, res) => {
const result = await confirmPendingTotpAtomically({
userId: req.user.id,
code: req.body.code,
rejectPreviouslyAcceptedStep: true
});
if (!result.ok) return res.status(400).json({ error: 'Invalid or expired code' });
res.json({ recoveryCodes: result.oneTimeRecoveryCodes });
});How do you handle login with MFA?
After the first factor, create a short-lived, single-purpose pre-authentication transaction bound to the browser, user, requested action and attempt counter. Do not issue a normal session or access/refresh tokens until the required factors pass. Rate-limit by account and broader risk signals, prevent TOTP replay, offer controlled recovery, rotate the final session ID and log meaningful events. Passkeys may satisfy both possession and user verification without a separate password, depending on assurance requirements.
app.post('/login/password', loginRateLimit, async (req, res) => {
const user = await verifyPasswordWithoutEnumeration(req.body);
if (!user) return res.status(401).json({ error: 'Invalid credentials' });
const transaction = await createPreAuthTransaction(user.id, requiredFactors(user));
res.json({ next: transaction.publicChallenge });
});
app.post('/login/totp', mfaRateLimit, async (req, res) => {
const transaction = await consumeValidPreAuthStep(req.body.challenge, req.body.code);
if (!transaction.complete) return res.status(401).json({ error: 'Invalid challenge' });
await establishRotatedSession(req, transaction.userId);
res.sendStatus(204);
});Security Best Practices Questions
These questions test your overall security awareness.
How do you implement rate limiting for authentication?
Throttling must address targeted guessing, credential stuffing and distributed password spraying without giving attackers an easy account-lockout denial of service. Combine account-level controls with trustworthy network/device risk signals, progressive delay or risk-based challenges, breached-password detection, MFA and monitoring. Normalize identifiers, use a distributed atomic store, do not trust unsanitized forwarding headers, keep outward login responses generic and let legitimate users recover. Tune thresholds from traffic and threat data rather than copying a universal “five attempts” rule.
app.post('/login', async (req, res) => {
const accountKey = keyedHash(normalizeIdentifier(req.body.email));
const networkKey = trustedClientNetwork(req); // Proxy chain configured explicitly.
const decision = await distributedAuthLimiter.checkAndIncrement({ accountKey, networkKey });
if (!decision.allowed) {
await constantShapeDelay();
return res.status(429).json({ error: 'Unable to sign in' });
}
// Perform equal-shaped account/password verification and record outcome/risk.
return completeLoginWithoutEnumeration(req, res);
});How would you implement "Remember Me" functionality?
“Remember me” means choosing a persistent login lifecycle, not necessarily a JWT refresh token. Record the user's informed choice, use a rotating per-device session or grant with idle and absolute expiry, expose active devices, support individual/all-device revocation and require reauthentication for sensitive changes. Do not extend a rotated credential beyond its authorized maximum lifetime. Pick lifetimes from risk, regulation and recovery capability rather than fixed 7/30-day folklore.
How would you design authentication for a banking app?
Start from the bank's transactions, regulatory obligations, fraud model and assurance targets. Prefer phishing-resistant authenticators such as passkeys/security keys where deployable; provide hardened enrollment, recovery and support channels. Use risk-aware session idle/absolute limits, rotation, device/session management and step-up or transaction authorization that clearly binds payee and amount. Apply layered abuse controls, signed/auditable security events, anomaly detection and rehearsed key/session revocation.
IP addresses and device fingerprints are noisy, privacy-sensitive signals, not hard session bindings that strand mobile users or become authorization. Use TLS correctly; mobile certificate pinning is an operational trade-off, not a blanket requirement, because broken rotation and recovery can create availability and update risk. Independent review, threat modeling and tested incident/fraud response matter more than copied timeout constants.
Quick Reference
| Concept | Implementation | Security Note |
|---|---|---|
| Password storage | Calibrated Argon2id; legacy/FIPS alternatives as required | Salt; optional separate pepper; rehash plan |
| Browser session | High-entropy opaque ID in protected cookie | Rotate on privilege change; CSRF defense |
| Access token | Profiled opaque token or strictly validated JWT | Narrow audience/scope/lifetime; replay model |
| Refresh token | Optional, protected and grant-bound | Sender constrain or rotate with reuse detection |
| Password reset | High-entropy, keyed-hash-at-rest, atomic single use | Generic response; short risk-based expiry |
| MFA | Prefer phishing-resistant WebAuthn; TOTP as supported fallback | Secure enrollment and recovery |
Frequently Asked Questions
What is the difference between authentication and authorization?
Authentication establishes or verifies an identity; authorization decides whether a subject may perform an action on a resource in the current context. A system can authorize anonymous access, so authentication is not literally required for every decision. For protected operations, return 401 when valid authentication credentials are missing or invalid and 403 when the authenticated principal lacks permission.
When should you use JWT vs session-based authentication?
Do not choose JWT merely because an API, SPA, mobile client or microservice is involved. An opaque session cookie or browser BFF is often simpler when the application needs central revocation and tokens need not cross trust boundaries. Use JWT when independently operated recipients need a standardized, signed claim set and can enforce issuer, audience, type, algorithm, lifetime and key rules. Either design can scale and either may require server-side state.
What is a refresh token and why is it needed?
A refresh token represents an authorization grant and lets an eligible client obtain access tokens without repeating user authentication. It is optional and more valuable than a single short-lived access token, so protect it in transit and storage, bind it to the client and grant, limit its lifetime and scope, and support revocation. Public clients need sender-constrained refresh tokens or rotation with reuse detection; simply minting another token is not rotation.
How does OAuth 2.0 work?
OAuth 2.0 delegates authorization; it does not by itself authenticate a user. In a modern interactive flow, the client sends the browser to an authorization server, validates the returned transaction, and exchanges a one-time authorization code using PKCE for an access token intended for a resource server. OpenID Connect adds authentication and an ID token for the client. Client Credentials represents a client acting on its own behalf, not a user login.
Where should you store JWT tokens in a browser?
Prefer an HttpOnly, Secure, narrowly scoped session cookie and a backend-for-frontend that keeps OAuth tokens out of browser JavaScript. HttpOnly limits token exfiltration but an XSS payload can still act as the user, while automatic cookies require CSRF defenses such as SameSite plus a token or strict origin checks where appropriate. Do not put authentication credentials in localStorage or sessionStorage; if JavaScript must hold an access token, keep exposure short and design for compromise.
What are common authentication security vulnerabilities?
Common failures include credential stuffing and account enumeration, weak recovery and MFA enrollment, session fixation, missing CSRF defenses, insecure browser token storage, overbroad tokens, and JWT validation that checks a signature but not issuer, audience, type, algorithm or lifetime. Use maintained identity components, TLS, adaptive password hashing, layered throttling, session rotation and revocation, phishing-resistant MFA where warranted, safe recovery, audit events and tested key rotation.
Sources
- RFC 7519: JSON Web Token (JWT)
- RFC 8725: JSON Web Token Best Current Practices
- RFC 9700: Best Current Practice for OAuth 2.0 Security
- RFC 10017: OAuth 2.0 for Browser-Based Applications
- OpenID Connect Core 1.0
- NIST SP 800-63B-4: Authentication and Authenticator Management
- OWASP Password Storage Cheat Sheet
- OWASP Session Management Cheat Sheet
- OWASP Forgot Password Cheat Sheet
- OWASP Multifactor Authentication Cheat Sheet
Related Articles
- Complete Node.js Backend Developer Interview Guide - comprehensive preparation guide for backend interviews
- WebSockets & Socket.IO Interview Guide - Authenticating WebSocket connections
- Web Security & OWASP Interview Guide - Security vulnerabilities and prevention
- Express.js Middleware Interview Guide - Middleware patterns for auth implementation
- REST API Interview Guide - API design including authentication
