Authentication & Authorization (JWT, OAuth, Passport)
5 questions found
How do JWTs, OAuth, and Passport.js fit together in a typical Node.js authentication architecture?
Intermediate
They operate at different layers: OAuth 2.0 is the protocol governing how a user grants a third-party application access without sharing their password directly; JWT is a token format commonly used to represent the resulting authenticated session or access grant in a compact, verifiable way; and Passport.js is a middleware library providing a consistent interface for implementing many different authentication strategies (including OAuth providers and JWT verification) within an Express application, so they're often used together rather than being competing choices.
const passport = require('passport');
const { Strategy: JwtStrategy, ExtractJwt } = require('passport-jwt');
passport.use(new JwtStrategy({
jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
secretOrKey: process.env.JWT_SECRET,
}, async (payload, done) => {
const user = await User.findById(payload.sub);
return user ? done(null, user) : done(null, false);
}));
Real-world example
An application uses Passport's GoogleStrategy for the initial OAuth login handshake with Google, then issues its own JWT for subsequent API requests, verified on each request by Passport's JwtStrategy -- combining all three pieces into one coherent authentication flow.
Common follow-ups: Why does the application issue its own JWT rather than just continuing to use Google's access token directly for every API call?;How does Passport's serializeUser/deserializeUser differ when using JWTs (stateless) versus sessions (stateful)?
Security;Express & Middleware
What is token introspection, and when is it needed instead of simply verifying a JWT's signature locally?
Advanced
Token introspection is a live call to the authorization server (via a defined OAuth endpoint) to ask 'is this token still valid, and what does it represent?' -- necessary for opaque tokens that aren't self-contained JWTs (and so can't be verified locally at all), and sometimes used even with JWTs when a resource server needs to check real-time revocation status that a JWT's own signature alone can't reflect, since a JWT's local verification only confirms it hasn't been tampered with, not that it hasn't been revoked since issuance.
const response = await fetch('https://auth-server.com/introspect', {
method: 'POST',
headers: { Authorization: `Basic ${clientCredentials}` },
body: new URLSearchParams({ token: accessToken }),
});
const { active, sub, scope } = await response.json();
if (!active) return res.status(401).json({ error: 'Token revoked or expired' });
Real-world example
A resource server handling highly sensitive financial data uses token introspection on every request despite the added latency, specifically because it needs to guarantee immediate revocation enforcement, which local JWT signature verification alone cannot provide.
Common follow-ups: What's the performance tradeoff of introspection (a network call per request) compared to local JWT verification?;How do short JWT expiration times partially reduce, but not eliminate, the need for introspection?
Security;HTTP & HTTPS Modules
How would you implement 'Sign in with GitHub' using Passport's OAuth strategy in an Express application?
Intermediate
Using passport-github2 (or a similar strategy package), you register the strategy with your app's client ID and secret, define a callback that receives the authenticated GitHub profile and finds-or-creates a corresponding local user record, and wire up two routes: one that redirects the user to GitHub to begin the OAuth flow, and one matching GitHub's configured callback URL that completes the exchange.
passport.use(new GitHubStrategy({
clientID: process.env.GITHUB_CLIENT_ID,
clientSecret: process.env.GITHUB_CLIENT_SECRET,
callbackURL: '/auth/github/callback',
}, async (accessToken, refreshToken, profile, done) => {
const user = await User.findOrCreate({ githubId: profile.id, name: profile.displayName });
done(null, user);
}));
app.get('/auth/github', passport.authenticate('github'));
app.get('/auth/github/callback', passport.authenticate('github', { failureRedirect: '/login' }), (req, res) => res.redirect('/dashboard'));
Real-world example
A developer-tools SaaS product offers 'Sign in with GitHub' as its only authentication method, using Passport's GitHub strategy to handle the entire OAuth handshake and automatically create a local user record on a developer's very first login.
Common follow-ups: How does the findOrCreate pattern handle a user who signs in with GitHub after already having an account created via email/password?;What GitHub OAuth scopes would need to be requested to also read the user's repositories, beyond basic profile info?
Databases & ORMs (MongoDB/Mongoose
SQL/Sequelize);Security
What is PKCE (Proof Key for Code Exchange), and why is it now recommended for OAuth flows even in traditional server-side Node.js applications?
Advanced
PKCE adds a dynamically generated secret (a 'code verifier' and its hashed 'code challenge') to the Authorization Code flow, binding the initial authorization request to the specific client that later exchanges the code for a token -- originally designed for public clients (like mobile and single-page apps) that can't securely store a static client secret, but now recommended universally by OAuth security best practices as defense-in-depth, protecting against authorization code interception even for confidential server-side clients.
const crypto = require('node:crypto');
const codeVerifier = crypto.randomBytes(32).toString('base64url');
const codeChallenge = crypto.createHash('sha256').update(codeVerifier).digest('base64url');
// codeChallenge sent in the initial authorization request
// codeVerifier sent later, during the token exchange, to prove it's the same client
Real-world example
A company's server-side Node.js application adds PKCE to its existing Authorization Code flow with Okta, following updated OAuth 2.1 security recommendations, providing an additional layer of protection against authorization code interception even though the server already keeps its client secret confidential.
Common follow-ups: How does PKCE protect against an intercepted authorization code even without needing a client secret at all?;What's the difference between the 'plain' and 'S256' PKCE code challenge methods?
Security;HTTP & HTTPS Modules
How do you validate a JWT's claims (issuer, audience, expiration) beyond just verifying its cryptographic signature?
Intermediate
Verifying a JWT's signature alone only confirms it hasn't been tampered with and was signed by the expected key -- a complete verification also needs to check the 'exp' claim (has it expired?), the 'iss' claim (was it issued by the expected authorization server?), and the 'aud' claim (was this token actually intended for this specific API, not a different one?), since a validly signed token intended for a different audience could otherwise be replayed against an API it was never meant to authenticate against.
const decoded = jwt.verify(token, publicKey, {
issuer: 'https://auth.example.com',
audience: 'my-api',
algorithms: ['RS256'], // explicitly restrict allowed algorithms too
});
// jwt.verify() throws automatically if any of these claims don't match
Real-world example
A company running multiple internal APIs discovers that a JWT issued for their billing API could technically also be replayed successfully against their user-profile API, since neither service was checking the 'aud' claim; adding strict audience validation to every API closes this cross-service token replay vulnerability.
Common follow-ups: Why is explicitly restricting the 'algorithms' option important, given the notorious 'alg: none' JWT vulnerability?;How do issuer and audience validation together prevent a token issued for one system from being accepted by an unrelated one?
Security;Microservices Architecture with Node.js