Skip to content

πŸ” Authentication and Authorization ​

BeginnerBeginner0–1 yr experience Foundational concepts explained from first principles. No prior system-design experience required.Prerequisites: Comfortable reading code. Familiarity with at least one programming language.

Foundation concept β€” required reading for every system.

Authentication (AuthN) answers "Who are you?" and Authorization (AuthZ) answers "What can you do?". They are two separate concerns that must be implemented together β€” and getting either one wrong compromises the entire system.


πŸ“‹ Table of Contents ​

  1. The Core Difference β€” AuthN vs AuthZ
  2. Where They Sit in the System
  3. Authentication Strategies
  4. Authorization Models
  5. Token-Based Authentication with JWT
  6. Session-Based Authentication
  7. OAuth 2.0 and OpenID Connect
  8. Password Storage and Hashing
  9. Multi-Factor Authentication (MFA)
  10. Common Vulnerabilities and Defenses
  11. Best Practices Checklist
  12. Related Topics

1. The Core Difference β€” AuthN vs AuthZ ​

These two terms are often confused but represent distinct responsibilities.

AspectAuthentication (AuthN)Authorization (AuthZ)
QuestionWho are you?What are you allowed to do?
VerifiesIdentity (user, service, device)Permissions (roles, scopes, policies)
HappensFirst (before anything else)After successful auth
Common methodsPasswords, tokens, biometrics, OAuth, SAMLRBAC, ABAC, ACLs, scopes, resource ownership
Failure returns401 Unauthorized403 Forbidden
ExampleLogin with email + password returns JWTAdmin role can DELETE /users/:id

TIP

The status codes matter β€” 401 means "I don't know who you are" and 403 means "I know who you are but you can't do that." Returning the wrong one leaks information.


2. Where They Sit in the System ​

Authentication and authorization are typically enforced at the edge (API Gateway / Load Balancer) and re-checked at the service level.

Why enforce at both layers? ​

LayerWhat it checksWhy
Edge / GatewayToken signature, expiry, basic roleReject invalid traffic before it hits your services
ServiceResource ownership, fine-grained permissionsDefense in depth β€” never trust the gateway blindly
DatabaseRow-level security (optional)Last line of defense for highly sensitive data

WARNING

Never trust only the gateway. Any service that has access to internal data should re-verify the caller's identity and permissions. A compromised internal service should still be unable to read another tenant's data.


3. Authentication Strategies ​

Decision Matrix ​

StrategyBest forProsCons
Session + CookieServer-rendered web appsSimple, revocableStateful, CSRF risk
JWT (stateless)SPAs, mobile, microservicesScalable, no shared stateHard to revoke, larger tokens
OAuth 2.0 / OIDCThird-party login, federated identityNo password storageComplex setup
API KeyServer-to-server, machine identitySimple, easy to rotateNo user context, leak risk
SAMLEnterprise SSOMature, enterprise-gradeXML-heavy, complex
mTLSService-to-service in zero-trust networksStrongest crypto identityPKI overhead

4. Authorization Models ​

4.1 RBAC β€” Role-Based Access Control ​

Roles are assigned to users; permissions are assigned to roles. Simple, easy to reason about.

javascript
// RBAC middleware β€” Node.js
function authorize(...allowedRoles) {
  return (req, res, next) => {
    if (!req.user) {
      return res.status(401).json({ error: "Authentication required" });
    }
    if (!allowedRoles.includes(req.user.role)) {
      return res.status(403).json({ error: "Insufficient role" });
    }
    next();
  };
}

// Usage
app.delete("/users/:id", authenticate, authorize("admin"), deleteUser);
app.post("/posts", authenticate, authorize("admin", "editor"), createPost);
app.get(
  "/posts",
  authenticate,
  authorize("admin", "editor", "viewer"),
  listPosts
);

4.2 ABAC β€” Attribute-Based Access Control ​

Policies are evaluated against attributes of the user, resource, and environment. More flexible, more complex.

javascript
// ABAC example β€” policy-as-code
const policies = {
  canEditPost: (user, post, context) => {
    return (
      user.id === post.authorId || // author can edit own
      user.role === "admin" || // admin can edit any
      (user.role === "editor" && context.timeSincePublish < 60 * 60) // editors within 1h
    );
  },
};

function checkPolicy(policyName, user, resource, context = {}) {
  const policy = policies[policyName];
  if (!policy) throw new Error(`Unknown policy: ${policyName}`);
  return policy(user, resource, context);
}

4.3 ACL and Resource Ownership ​

For per-resource checks (e.g., "can this user delete this specific post"), the service must own the authorization decision.

Choosing the Right Model ​

ModelWhen to use
RBACSmall/medium apps, clear role hierarchy, most common
ABACComplex rules (time, location, ownership), enterprise systems
ACLPer-resource permissions, multi-tenant apps
PBACRegulated industries (HIPAA, finance) requiring audit trails

5. Token-Based Authentication with JWT ​

JWT (JSON Web Token) is the de-facto standard for stateless authentication in modern web and mobile apps.

5.1 Anatomy of a JWT ​

A JWT has three base64-encoded parts separated by dots:

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  HEADER.PAYLOAD.SIGNATURE                                    β”‚
β”‚  eyJhbGciOiJIUzI1NiJ9.eyJ1c2VyIjoxMjN9.abc123sig             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
javascript
// Header
{
  "alg": "HS256",   // signing algorithm
  "typ": "JWT"
}

// Payload (claims β€” public, not encrypted!)
{
  "sub": "user_123",        // subject (user id)
  "email": "[email protected]",
  "role": "admin",
  "iat": 1700000000,        // issued at
  "exp": 1700000900,        // expires in 15min
  "iss": "api.example.com", // issuer
  "aud": "web-app"          // audience
}

WARNING

The JWT payload is signed, not encrypted. Anyone with the token can decode it. Never put passwords, secrets, or PII in the payload. Use JWE if you need encryption.

5.2 Complete JWT Implementation ​

javascript
// utils/jwt.js
const jwt = require("jsonwebtoken");
const crypto = require("crypto");

const ACCESS_SECRET = process.env.JWT_ACCESS_SECRET; // 256-bit random
const REFRESH_SECRET = process.env.JWT_REFRESH_SECRET; // different 256-bit

// Generate access token (short-lived, 15 min)
function generateAccessToken(user) {
  return jwt.sign(
    { sub: user.id, role: user.role, email: user.email },
    ACCESS_SECRET,
    {
      expiresIn: "15m",
      issuer: "api.example.com",
      audience: "web-app",
      algorithm: "HS256",
    }
  );
}

// Generate refresh token (long-lived, 7 days)
function generateRefreshToken(userId) {
  return jwt.sign({ sub: userId, type: "refresh" }, REFRESH_SECRET, {
    expiresIn: "7d",
    issuer: "api.example.com",
  });
}

// Verify token β€” always catch and inspect errors
function verifyAccessToken(token) {
  return jwt.verify(token, ACCESS_SECRET, {
    issuer: "api.example.com",
    audience: "web-app",
    algorithms: ["HS256"], // explicit β€” prevent alg confusion attacks
  });
}
javascript
// middleware/auth.js
const { verifyAccessToken } = require("../utils/jwt");

async function authenticate(req, res, next) {
  try {
    const header = req.headers.authorization;
    if (!header?.startsWith("Bearer ")) {
      return res.status(401).json({ error: "Missing token" });
    }

    const token = header.slice(7);
    const decoded = verifyAccessToken(token);

    // Optional: re-check user still exists / is active
    // const user = await db.users.findById(decoded.sub);
    // if (!user || user.banned) return res.status(401).json({ error: "Invalid" });

    req.user = { id: decoded.sub, role: decoded.role, email: decoded.email };
    next();
  } catch (err) {
    if (err.name === "TokenExpiredError") {
      return res
        .status(401)
        .json({ error: "Token expired", code: "TOKEN_EXPIRED" });
    }
    return res.status(401).json({ error: "Invalid token" });
  }
}

5.3 Refresh Token Flow ​

javascript
// Refresh token endpoint
app.post("/auth/refresh", async (req, res) => {
  const refreshToken = req.cookies.refreshToken;
  if (!refreshToken) return res.status(401).json({ error: "No refresh token" });

  try {
    const decoded = jwt.verify(refreshToken, REFRESH_SECRET);
    const stored = await db.refreshTokens.findOne({
      token: hash(refreshToken),
      userId: decoded.sub,
      revoked: false,
      expiresAt: { $gt: new Date() },
    });

    if (!stored)
      return res.status(401).json({ error: "Invalid refresh token" });

    // Rotate: issue new refresh token, revoke old one
    const newRefresh = generateRefreshToken(decoded.sub);
    await db.refreshTokens.update(
      { id: stored.id },
      { revoked: true, replacedBy: hash(newRefresh) }
    );
    await db.refreshTokens.insert({
      userId: decoded.sub,
      token: hash(newRefresh),
      expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
    });

    const accessToken = generateAccessToken({
      id: decoded.sub,
      role: stored.role,
    });
    res.cookie("refreshToken", newRefresh, {
      httpOnly: true,
      secure: true, // HTTPS only
      sameSite: "strict",
      maxAge: 7 * 24 * 60 * 60 * 1000,
    });
    res.json({ accessToken });
  } catch {
    res.status(401).json({ error: "Invalid refresh token" });
  }
});

5.4 Where to Store the Token ​

StorageXSS safe?CSRF safe?Best for
localStorageβŒβœ…None β€” avoid for tokens
sessionStorageβŒβœ…None β€” avoid for tokens
httpOnly cookieβœ…βš οΈ need CSRF tokenRecommended for web apps
Memory (JS var)βœ…βœ…SPAs with refresh-token flow

6. Session-Based Authentication ​

The classic approach: server stores session state, client gets an opaque session ID via cookie.

When to choose sessions over JWT ​

  • Server-rendered web apps (e.g., Django, Rails, Express + EJS)
  • You need immediate revocation (logout, ban, password change)
  • Simpler to debug (no token decoding)
  • Compliance requires audit trail of active sessions

Redis Session Implementation ​

javascript
const session = require("express-session");
const RedisStore = require("connect-redis").default;
const { createClient } = require("redis");

const redisClient = createClient({ url: process.env.REDIS_URL });
await redisClient.connect();

app.use(
  session({
    store: new RedisStore({ client: redisClient }),
    secret: process.env.SESSION_SECRET,
    resave: false,
    saveUninitialized: false,
    cookie: {
      httpOnly: true,
      secure: process.env.NODE_ENV === "production",
      sameSite: "strict",
      maxAge: 1000 * 60 * 60 * 24, // 24 hours
    },
  })
);

7. OAuth 2.0 and OpenID Connect ​

OAuth 2.0 is an authorization framework for delegated access. OpenID Connect (OIDC) is an authentication layer built on top of OAuth 2.0.

7.1 OAuth 2.0 Roles ​

7.2 OAuth 2.0 Grant Flows ​

GrantUse CaseWhen to Pick
Authorization Code + PKCEWeb apps, mobile apps, SPAsDefault for all modern apps
Client CredentialsServer-to-server, no userMicroservices, cron jobs, background workers
Device CodeSmart TVs, IoT, CLI toolsDevices without a browser
Refresh TokenGet new access token silentlyAlways paired with short-lived access tokens

7.3 Authorization Code Flow with PKCE ​

7.4 OAuth vs OIDC β€” What's the Difference? ​

AspectOAuth 2.0OpenID Connect
PurposeAuthorization (scopes)Authentication (identity)
TokenAccess tokenID token (JWT with user info)
Endpoint/authorize, /token/userinfo (adds identity)
Use when"Let me act on behalf of the user""Let me know who the user is"

TIP

Almost always use OIDC when you want "Sign in with Google/GitHub." Use plain OAuth 2.0 only when you need delegated access without identity (e.g., a third-party app posting to your user's Twitter).


8. Password Storage and Hashing ​

8.1 The Wrong Ways (and Why) ​

javascript
// ❌ NEVER do any of these
const plain = "mypassword123";
const md5 = crypto.createHash("md5").update(plain).digest("hex");
const sha1 = crypto.createHash("sha1").update(plain).digest("hex");
const sha256 = crypto.createHash("sha256").update(plain).digest("hex");
const encrypted = encryptAES(plain, key); // reversible β€” no!

8.2 The Right Way β€” bcrypt / Argon2id ​

javascript
const bcrypt = require("bcrypt");

// Hashing on signup (cost factor 12 β€” ~250ms per hash)
async function hashPassword(plaintext) {
  const salt = await bcrypt.genSalt(12);
  return bcrypt.hash(plaintext, salt);
}

// Verification on login
async function verifyPassword(plaintext, hash) {
  return bcrypt.compare(plaintext, hash);
}

// Alternative: Argon2id (winner of PHC, OWASP recommended)
const argon2 = require("argon2");

async function hashPasswordArgon2(plaintext) {
  return argon2.hash(plaintext, {
    type: argon2.argon2id,
    memoryCost: 65536, // 64 MB
    timeCost: 3,
    parallelism: 4,
  });
}

8.3 Password Policy β€” Show, Don't Just Tell ​

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Password Requirements                                       β”‚
β”‚  ─────────────────────────────────────────────────────────   β”‚
β”‚  βœ“ At least 12 characters                                   β”‚
β”‚  βœ“ Must contain uppercase, lowercase, number, symbol         β”‚
β”‚  βœ“ Cannot contain your email or username                     β”‚
β”‚  βœ“ Cannot be one of the top 10,000 common passwords          β”‚
β”‚  βœ“ Cannot be reused (last 5 passwords)                       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
AlgorithmRecommended?Why
Argon2idβœ… BestMemory-hard, PHC winner, GPU-resistant
bcryptβœ… GoodBattle-tested, widely available
scryptβœ… GoodMemory-hard, but less widely supported
PBKDF2⚠️ AcceptableOnly when Argon2/bcrypt not available; high iterations
MD5 / SHA-1 / SHA-256❌ NEVERTrivially fast, GPU-cracked

9. Multi-Factor Authentication (MFA) ​

Defense in depth: require two or more of something you know, have, or are.

MFA Flow β€” TOTP (Time-Based One-Time Password) ​

javascript
// TOTP β€” using otplib
const { authenticator } = require("otplib");

authenticator.options = { window: 1, step: 30 }; // 30s steps, Β±1 window tolerance

// On MFA enrollment
function generateSecret(userEmail) {
  const secret = authenticator.generateSecret();
  const otpauth = authenticator.keyuri(userEmail, "YourApp", secret);
  return { secret, otpauth }; // User scans otpauth as QR code
}

// On MFA verification
function verifyToken(token, secret) {
  return authenticator.verify({ token, secret });
}

MFA Methods Compared ​

MethodSecurityUXCost
Hardware key (FIDO2/WebAuthn)⭐⭐⭐⭐⭐⭐⭐⭐⭐$$ (keys)
TOTP (Authenticator app)⭐⭐⭐⭐⭐⭐⭐⭐Free
SMS code⭐⭐⭐⭐⭐$ (per SMS)
Email code⭐⭐⭐Free
Push notification⭐⭐⭐⭐⭐⭐⭐⭐⭐$$ (provider)

WARNING

SMS is vulnerable to SIM-swap attacks β€” only use it as a last resort. Prefer TOTP or hardware keys for high-value accounts.


10. Common Vulnerabilities and Defenses ​

10.1 Attack Surface Map ​

10.2 Defense Patterns ​

javascript
// 1. Rate limiting on auth endpoints
const rateLimit = require("express-rate-limit");
const authLimiter = rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 5, // 5 attempts per 15 min per IP
  message: { error: "Too many attempts. Try again later." },
  standardHeaders: true,
  legacyHeaders: false,
});
app.post("/login", authLimiter, loginHandler);
app.post("/register", authLimiter, registerHandler);

// 2. Account lockout after failed attempts
async function trackFailedLogin(userId) {
  const user = await db.users.findById(userId);
  user.failedAttempts = (user.failedAttempts || 0) + 1;
  if (user.failedAttempts >= 5) {
    user.lockedUntil = new Date(Date.now() + 15 * 60 * 1000); // 15 min
  }
  await user.save();
}

// 3. Never log tokens or passwords
logger.info("Login attempt", { userId, email }); // βœ…
logger.info("Login attempt", { password }); // ❌ NEVER
logger.info("Login attempt", { token }); // ❌ NEVER

// 4. JWT algorithm confusion prevention
jwt.verify(token, SECRET, { algorithms: ["HS256"] }); // βœ… explicit

// 5. CSRF protection for cookie-based sessions
const csrf = require("csurf");
app.use(csrf({ cookie: true }));
// Send token in meta tag, send in header on every mutation

10.3 Security Headers ​

javascript
const helmet = require("helmet");
app.use(
  helmet({
    contentSecurityPolicy: {
      directives: {
        defaultSrc: ["'self'"],
        scriptSrc: ["'self'"],
        objectSrc: ["'none'"],
        frameAncestors: ["'none'"],
      },
    },
    hsts: { maxAge: 31536000, includeSubDomains: true, preload: true },
  })
);

11. Best Practices Checklist ​

βœ… Authentication ​

  • [ ] Use HTTPS everywhere β€” never send credentials over plain HTTP
  • [ ] Hash passwords with Argon2id or bcrypt (cost β‰₯ 12)
  • [ ] Enforce strong password policy + check against known breached passwords
  • [ ] Implement rate limiting on login/register/reset endpoints
  • [ ] Lock account after N failed attempts (exponential backoff)
  • [ ] Use short-lived access tokens (15 min) + long-lived refresh tokens (7 days)
  • [ ] Store refresh tokens in httpOnly + Secure + SameSite=Strict cookies
  • [ ] Rotate refresh tokens on every use
  • [ ] Implement MFA for high-value accounts
  • [ ] Log all auth events (success, failure, lockout) β€” never log secrets
  • [ ] Implement PKCE for all OAuth 2.0 flows
  • [ ] Explicitly list algorithms in jwt.verify() to prevent alg attacks

βœ… Authorization ​

  • [ ] Never trust client-side authorization checks
  • [ ] Re-verify permissions at the service layer, not just the gateway
  • [ ] Use least privilege β€” default to deny
  • [ ] Prefer RBAC for clarity; reach for ABAC only when you need fine-grained rules
  • [ ] Implement resource ownership checks for per-user data
  • [ ] Use scopes in OAuth tokens to limit delegated access
  • [ ] Return 403 (not 404) when the user exists but lacks permission β€” but only if no info leak is possible
  • [ ] Audit sensitive operations (who accessed what, when)

βœ… Operational ​

  • [ ] Store secrets in a secret manager (AWS Secrets Manager, Vault) β€” never in code
  • [ ] Rotate JWT signing keys periodically
  • [ ] Have a token revocation strategy (blacklist or short TTL)
  • [ ] Monitor for anomalous patterns (login from new geo, impossible travel)
  • [ ] Run penetration tests and dependency audits regularly
  • [ ] Document your incident response plan for credential leaks

This page sits at the foundation of the security stack. To go deeper, follow these threads:

TopicWhere to go next
HTTPS / TLSNetworking β€” Level 1
Rate LimitingScalability β€” Level 2
API GatewayMicroservices β€” Level 6
Distributed session storeCaching β€” Level 4
Security in interviewsSecurity Interview β€” Level 12
Design patterns (Strategy, Decorator)Design Patterns β€” Level 1

🧠 One-Page Recap ​

text
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  AUTHENTICATION                β”‚  AUTHORIZATION                     β”‚
β”‚  ─────────────                 β”‚  ─────────────                     β”‚
β”‚  β€’ Who are you?                β”‚  β€’ What can you do?                β”‚
β”‚  β€’ 401 if unknown              β”‚  β€’ 403 if forbidden                β”‚
β”‚  β€’ JWT / Session / OAuth       β”‚  β€’ RBAC / ABAC / ownership         β”‚
β”‚  β€’ Verify before anything else β”‚  β€’ Check at gateway AND service    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚  STORAGE    β†’ bcrypt/Argon2    β”‚  CHECKS  β†’ server-side, not client β”‚
β”‚  TRANSPORT  β†’ HTTPS only       β”‚  TOKENS  β†’ short-lived access +    β”‚
β”‚  COOKIES    β†’ httpOnly/Secure  β”‚           refresh rotation         β”‚
β”‚  MFA        β†’ TOTP or WebAuthn β”‚  LEAST    β†’ privilege, default denyβ”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

INFO

Next step: Continue to Security β€” Interview Prep to see how these concepts are tested in FAANG interviews, or jump to Microservices β€” Level 6 to see how authentication is enforced across service boundaries.

Released under the ISC License.