Authentication & Authorization
Authentication proves who you are; authorization decides what you may do. This concept covers sessions, hashed passwords, JWT, refresh tokens, OAuth 2.0 and the role/permission model that production APIs use to guard resources.
What is this?
Authentication verifies identity using something you know (password), have (TOTP key) or are (biometric). Authorization then enforces what that identity may do — typically via roles, scopes or fine-grained permissions. The three mainstream approaches are opaque sessions stored server-side, signed stateless JWTs, and delegated OAuth 2.0/OIDC flows for third-party logins.
Why do you need it?
- Passwords must never be stored in plaintext; hashing with a salt (bcrypt/argon2) protects users when a database leaks.
- JWTs let any service verify a token without a session lookup, which simplifies scaling and microservice auth.
- Short-lived access tokens plus rotating refresh tokens limit the blast radius if a token is stolen.
- Separating authentication from authorization lets you grant or revoke permissions without logging users out.
Mental model
An airport boarding pass
A JWT access token
The pass proves you checked in (authenticated), names your gate (role), and is signed by the airline so it can't be forged. You show it at every gate (service) — each one just verifies the signature, no phone call to head office (session store) needed.
How it works
- On login, the server verifies the credential (password hash compare, or OAuth code exchange) and issues a credential: a session ID or a signed JWT.
- The client stores the credential (cookie or memory/secure storage) and sends it with each request, usually in the Authorization: Bearer header or a cookie.
- The server verifies it — session lookup, or JWT signature + expiry check — and establishes the identity.
- An authorization layer (middleware) maps identity to roles or scopes and checks them against the resource's required permissions.
- On expiry or logout, the access token is dropped and a refresh token (or session revoke) obtains a new one without re-entering the password.
JWT authentication flow across login, requests and refresh.
Key commands
npm install jsonwebtoken bcryptInstall JWT signing/verification and password hashing libraries.
added 12 packages in 1s
openssl rand -base64 48Generate a cryptographically random secret for signing tokens.
q3VtFp9x... (random 48 bytes base64)
node -e "console.log(require('bcrypt').hashSync('hunter2', 12))"Hash a password with a salt using 12 bcrypt rounds.
$2b$12$C6UzMDM.H6GQ7vp... (the salted hash)
curl -s -H 'Authorization: Bearer <token>' https://api.example.com/meCall an endpoint with a JWT in the Authorization header.
{"sub":"user_42","role":"admin"}Examples
Issue and verify a JWT with Node
const jwt = require("jsonwebtoken") const SECRET = process.env.JWT_SECRET // must come from env, never source // Issue: sign an access token valid for 15 minutesconst accessToken = jwt.sign( { sub: "user_42", role: "admin", scopes: ["todos:write"] }, SECRET, { expiresIn: "15m" }) // Verify: throw if expired or signature invalidfunction verifyToken(token) { try { const payload = jwt.verify(token, SECRET) return payload } catch (err) { throw new Error("invalid or expired token") }} console.log(verifyToken(accessToken).role) // "admin"jwt.sign produces a base64 payload signed with the secret. jwt.verify recomputes the signature and checks the exp claim, so a tampered or expired token throws. Because the token is self-contained, any service that knows the secret can verify it without touching a database — the trade-off is you cannot revoke it before it expires.
In production
- SPAs and mobile apps authenticate with an authorization server (Auth0, Cognito, Keycloak) and attach JWTs to every API call.
- Refresh-token rotation with httpOnly cookies is the standard pattern for long-lived web sessions.
- OAuth 2.0 + OIDC gives 'Sign in with Google/GitHub' and lets third-party apps act on behalf of users with scoped tokens.
- Service-to-service auth uses client-credentials flows or mTLS instead of user passwords.
Best practices
- Hash passwords with bcrypt/argon2 (cost factor >= 10) — never encrypt or store plaintext.
- Keep JWTs small, put only non-sensitive claims in them, and set short expiries (minutes to an hour).
- Use RS256 with a public/private key pair so services verify without the signing key.
- Store access tokens in memory on the client; use httpOnly + SameSite cookies and CSRF protection for session-based auth.
- Implement rate limiting and account lockout on the login endpoint to slow brute-force attacks.
Common mistakes
- Putting secrets or a JWT_SECRET literal in source code or client bundles.
- Using tokens as sessions: trying to 'log out' a JWT without a blocklist does not work.
- Checking only that a token is valid, not whether the user/role is allowed — returning 401 instead of 403 for authorization failures.
- Never invalidating tokens after a password change, leaving compromised sessions alive.
Common issues
jwt expiredThe access token is past its exp claim.
Have the client use the refresh token to mint a new access token.
invalid signatureToken signed with a different secret or key, or tampered.
Ensure both sides use the same secret/keyset and correct algorithm (HS256 vs RS256).
No auth token foundThe client sent no Authorization header or cookie.
Attach Authorization: Bearer <token> or ensure the cookie is being sent with the request.
Debugging tips
- Decode a JWT locally with jwt.io or jwt.decode() to inspect the payload and exp claim.
- Verify the signature algorithm: a token signed with HS256 will fail RS256 verification and vice versa.
- Check Authorization header spelling: 'Bearer' with a capital B is required by most parsers.
- Log sub, role and request path at the authorization middleware to trace deny decisions.
Interview checkpoint
Hands-on lab
- Hash and verify passwords with bcrypt
- Issue signed JWTs with expiry
- Build a requireAuth middleware
- Protect an endpoint and test 401 vs 200
Password hashing, JWT and an auth middleware
40 min · intermediate
Quick quiz
Which status code should an API return when a user is authenticated but lacks permission for an action?
Why should passwords be hashed with a salt using bcrypt or argon2?
A JWT access token can be instantly revoked before it expires without extra server-side infrastructure.
Cheat sheet
jwt.sign(payload, secret, { expiresIn: '15m' })Issue an access tokenjwt.verify(token, secret)Verify signature + expiryAuthorization: Bearer <token>Standard header formatbcrypt.hash(password, 12)Hash with a saltbcrypt.compare(password, hash)Verify a loginopenssl rand -base64 48Generate a signing secret