Why Security Matters
Why This Matters
The Problem: Every system connected to the internet is under constant attack. SQL injection, credential stuffing, API abuse, and data breaches cost companies billions annually.
The Solution: Defense in depth - multiple layers of security controls so that if one layer fails, others catch the threat.
Real Impact: The 2017 Equifax breach exposed 147 million records due to an unpatched vulnerability. The 2021 Colonial Pipeline attack shut down fuel supplies for the US East Coast. Security is not optional.
Real-World Analogy
Think of system security like airport security:
- Authentication = Checking your passport (proving who you are)
- Authorization = Checking your boarding pass (proving what you are allowed to access)
- Encryption = Sealed, tamper-evident luggage (data cannot be read or modified in transit)
- Rate Limiting = Crowd control (preventing one person from taking all the resources)
- Zero Trust = Security checkpoints at every gate, not just the entrance
Authentication vs Authorization
| Aspect | Authentication (AuthN) | Authorization (AuthZ) |
|---|---|---|
| Question | Who are you? | What can you do? |
| Example | Login with email/password | Can this user delete posts? |
| Happens | First (verify identity) | After authentication |
| Protocols | OAuth 2.0, SAML, OpenID Connect | RBAC, ABAC, ACLs, OAuth scopes |
| HTTP Status | 401 Unauthorized | 403 Forbidden |
| Token | ID Token (who you are) | Access Token (what you can do) |
Authorization Models
RBAC (Role-Based)
Assign permissions to roles (admin, editor, viewer), then assign roles to users. Simple and widely used. Works well for most applications.
ABAC (Attribute-Based)
Permissions based on attributes: user department, resource owner, time of day, IP address. More flexible but more complex than RBAC.
ReBAC (Relationship-Based)
Permissions based on relationships: "Can Alice view this doc? Yes, because she is in the shared folder's team." Used by Google Zanzibar (Drive, Docs).
OAuth 2.0 and OpenID Connect
OAuth 2.0 is the industry standard for authorization delegation. It lets users grant third-party applications limited access to their resources without sharing their password. OpenID Connect (OIDC) adds an identity layer on top for authentication.
JWT Tokens
JSON Web Tokens (JWTs) are a compact, URL-safe way to represent claims between two parties. They are the most common token format for modern APIs and single sign-on systems.
# JWT Authentication in Python
import jwt
import time
from datetime import datetime, timedelta
from functools import wraps
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET_KEY = "your-secret-key" # Use env var in production!
def create_token(user_id: str, role: str,
expires_minutes=30) -> str:
"""Create a signed JWT token."""
payload = {
"sub": user_id, # Subject (who)
"role": role, # Custom claim
"iat": datetime.utcnow(), # Issued at
"exp": datetime.utcnow() # Expiration
+ timedelta(minutes=expires_minutes),
"iss": "myapp.com", # Issuer
}
return jwt.encode(payload, SECRET_KEY, algorithm="HS256")
def verify_token(token: str) -> dict:
"""Verify and decode a JWT token."""
try:
payload = jwt.decode(
token, SECRET_KEY,
algorithms=["HS256"],
issuer="myapp.com"
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError("Token expired")
except jwt.InvalidTokenError:
raise AuthError("Invalid token")
def require_auth(allowed_roles=None):
"""Decorator for protecting endpoints."""
def decorator(f):
@wraps(f)
def wrapped(*args, **kwargs):
auth_header = request.headers.get("Authorization")
if not auth_header or not auth_header.startswith("Bearer "):
return jsonify({"error": "Missing token"}), 401
token = auth_header.split(" ")[1]
payload = verify_token(token)
# Check authorization (role-based)
if allowed_roles and payload["role"] not in allowed_roles:
return jsonify({"error": "Forbidden"}), 403
request.user = payload
return f(*args, **kwargs)
return wrapped
return decorator
# Usage
@app.route("/api/admin/users")
@require_auth(allowed_roles=["admin"])
def list_users():
return jsonify({"users": ["..."]})
JWT Security Pitfalls
- Never use "alg": "none": Some libraries accept unsigned tokens if the algorithm is set to "none." Always validate the algorithm.
- Keep tokens short-lived: Access tokens should expire in 15-30 minutes. Use refresh tokens for re-authentication.
- Cannot be revoked: Unlike session tokens, JWTs cannot be invalidated before expiry. Use a token blocklist for critical revocations.
- Use RS256 for microservices: Asymmetric signing (RS256) lets services verify tokens without sharing the secret key.
API Security Best Practices
| Threat | Mitigation | Implementation |
|---|---|---|
| SQL Injection | Parameterized queries | Never concatenate user input into SQL strings. Use ORM or prepared statements. |
| XSS | Output encoding + CSP | Encode all user-generated content. Set Content-Security-Policy headers. |
| CSRF | Anti-CSRF tokens + SameSite cookies | Include a unique token in forms. Set cookie SameSite=Strict. |
| Broken Auth | MFA + rate limiting + secure sessions | Enforce strong passwords, add 2FA, limit login attempts. |
| Data Exposure | Encryption + least privilege | TLS everywhere, encrypt sensitive fields, minimize API response data. |
| Mass Assignment | Explicit allowlists | Only accept fields you expect. Never bind request body directly to models. |
Encryption at Rest and in Transit
Encryption in Transit (TLS)
All network communication should use TLS 1.3. This prevents eavesdropping and man-in-the-middle attacks. Use HTTPS everywhere, including internal service-to-service calls.
Encryption at Rest
Encrypt data stored on disk: database fields, object storage, backups. Use AES-256 for symmetric encryption. Manage keys with AWS KMS, HashiCorp Vault, or cloud KMS.
Key Management
Never hardcode keys in source code. Rotate keys periodically. Use envelope encryption: encrypt data with a data key, then encrypt the data key with a master key.
Password Hashing
Never store passwords in plain text. Use bcrypt, scrypt, or Argon2 with appropriate cost factors. Salts are included automatically by these algorithms.
Zero Trust Architecture
The Zero Trust Principle
"Never trust, always verify." Traditional security creates a hard perimeter around the network (castle-and-moat). Zero Trust assumes the network is already compromised and verifies every request regardless of origin.
Zero Trust Key Principles
- Verify explicitly: Authenticate and authorize every request based on all available data (identity, location, device, data classification)
- Least privilege access: Grant minimum permissions needed. Use Just-In-Time (JIT) access for elevated privileges.
- Assume breach: Design as if attackers are already inside. Segment networks, encrypt everything, monitor continuously.
- Mutual TLS (mTLS): Both client and server present certificates. Used between microservices (Istio, Linkerd do this automatically).
- Micro-segmentation: Limit lateral movement. Each service can only communicate with its declared dependencies.
# OAuth 2.0 Authorization Code Flow (server-side)
import requests
import secrets
from flask import Flask, redirect, request, session
app = Flask(__name__)
app.secret_key = secrets.token_hex(32)
OAUTH_CONFIG = {
"client_id": "your-client-id",
"client_secret": "your-client-secret",
"auth_url": "https://auth.example.com/authorize",
"token_url": "https://auth.example.com/token",
"redirect_uri": "https://myapp.com/callback",
}
@app.route("/login")
def login():
# Step 1: Generate state for CSRF protection
state = secrets.token_urlsafe(32)
session["oauth_state"] = state
# Step 2: Redirect to authorization server
auth_url = (
f"{OAUTH_CONFIG['auth_url']}?"
f"response_type=code&"
f"client_id={OAUTH_CONFIG['client_id']}&"
f"redirect_uri={OAUTH_CONFIG['redirect_uri']}&"
f"scope=openid profile email&"
f"state={state}"
)
return redirect(auth_url)
@app.route("/callback")
def callback():
# Step 3: Verify state to prevent CSRF
if request.args.get("state") != session.get("oauth_state"):
return "State mismatch - possible CSRF", 403
# Step 4: Exchange auth code for tokens
code = request.args.get("code")
token_response = requests.post(
OAUTH_CONFIG["token_url"],
data={
"grant_type": "authorization_code",
"code": code,
"redirect_uri": OAUTH_CONFIG["redirect_uri"],
"client_id": OAUTH_CONFIG["client_id"],
"client_secret": OAUTH_CONFIG["client_secret"],
}
)
tokens = token_response.json()
session["access_token"] = tokens["access_token"]
session["id_token"] = tokens["id_token"]
return redirect("/dashboard")
Practice Problems
Medium Token Refresh Strategy
Design a token refresh mechanism for a mobile app:
- Access token expires in 15 minutes, refresh token in 7 days
- Handle the case where the user is offline when the access token expires
- Implement refresh token rotation (each refresh issues a new refresh token)
- Detect and handle refresh token theft
Store refresh tokens in a database with a token family ID. When a refresh token is reused (already rotated), invalidate the entire family - it means the token was stolen.
import uuid
class TokenService:
def __init__(self, db):
self.db = db
def issue_tokens(self, user_id):
family_id = str(uuid.uuid4())
access = create_token(user_id, exp=900) # 15 min
refresh = str(uuid.uuid4())
self.db.store_refresh_token(
token=refresh,
user_id=user_id,
family_id=family_id,
used=False,
expires=time.time() + 604800 # 7 days
)
return access, refresh
def refresh_access_token(self, refresh_token):
record = self.db.get_refresh_token(refresh_token)
if not record or record.expired:
raise AuthError("Invalid refresh token")
# THEFT DETECTION: token already used
if record.used:
# Invalidate entire token family!
self.db.revoke_family(record.family_id)
raise AuthError("Token reuse detected")
# Mark old token as used
self.db.mark_used(refresh_token)
# Issue new token pair (rotation)
access = create_token(record.user_id, exp=900)
new_refresh = str(uuid.uuid4())
self.db.store_refresh_token(
token=new_refresh,
user_id=record.user_id,
family_id=record.family_id,
used=False,
)
return access, new_refresh
Hard Design an Auth System
Design an authentication and authorization system for a SaaS platform with:
- Multi-tenant support (each company is isolated)
- SSO via SAML for enterprise customers
- Fine-grained permissions (can user X edit document Y in workspace Z?)
- API key authentication for programmatic access
Use a centralized identity provider. Tenant isolation via tenant_id in every token and database query. For fine-grained permissions, consider Google Zanzibar (relationship-based access control).
# SaaS Auth Architecture
# 1. IDENTITY PROVIDER (IdP)
# - Central auth service (or Auth0/Okta)
# - Supports: password, OAuth, SAML SSO
# - Issues JWTs with: user_id, tenant_id, roles
# 2. MULTI-TENANT ISOLATION
# - tenant_id in every JWT claim
# - Every DB query: WHERE tenant_id = ?
# - Row-level security in PostgreSQL
# - Separate encryption keys per tenant
# 3. SAML SSO for Enterprise
# - Enterprise configures their IdP (Okta, Azure AD)
# - Our service acts as SAML Service Provider
# - Map SAML attributes to internal roles
# 4. FINE-GRAINED PERMISSIONS (Zanzibar-style)
# Relationship tuples:
# (doc:123, editor, user:alice)
# (workspace:456, member, team:engineering)
# (team:engineering, member, user:alice)
# Check: can user:alice edit doc:123?
# -> Yes, direct relationship exists
# 5. API KEYS
# - Prefix: sk_live_xxxx (secret), pk_live_xxxx (public)
# - Hash stored in DB (never store raw key)
# - Scoped to specific permissions + tenant
# - Rate limited separately from user tokens
Medium Secure API Checklist
You are launching a new public API. Create a security checklist covering:
- Authentication and authorization
- Input validation
- Transport security
- Response security headers
Think about OWASP API Security Top 10. Cover authentication, input validation, encryption, rate limiting, logging, and CORS.
# API Security Checklist
# AUTHENTICATION
# [x] OAuth 2.0 / API keys for all endpoints
# [x] JWT validation (signature, expiry, issuer)
# [x] Rate limit login attempts (5/min)
# [x] MFA for admin operations
# INPUT VALIDATION
# [x] Validate all input types and lengths
# [x] Parameterized queries (no SQL injection)
# [x] Sanitize HTML output (prevent XSS)
# [x] Reject unexpected fields (mass assignment)
# [x] File upload: check type, size, scan for malware
# TRANSPORT
# [x] TLS 1.3 only (disable 1.0, 1.1, 1.2)
# [x] HSTS header (force HTTPS)
# [x] Certificate pinning for mobile apps
# RESPONSE HEADERS
# [x] Content-Security-Policy
# [x] X-Content-Type-Options: nosniff
# [x] X-Frame-Options: DENY
# [x] Strict-Transport-Security
# [x] CORS: restrict to known origins
# LOGGING & MONITORING
# [x] Log all auth events
# [x] Alert on unusual patterns
# [x] Never log passwords or tokens
Quick Reference
Authentication Protocol Comparison
| Protocol | Use Case | Token Format | Best For |
|---|---|---|---|
| OAuth 2.0 | Delegated authorization | Access token (opaque or JWT) | Third-party API access |
| OpenID Connect | Authentication + OAuth 2.0 | ID token (JWT) | Single sign-on, user identity |
| SAML 2.0 | Enterprise SSO | XML assertion | Corporate/enterprise login |
| API Keys | Server-to-server auth | Opaque string | Programmatic access, simple auth |
| mTLS | Service-to-service | X.509 certificates | Microservices, zero trust |
Security Principles Summary
Core Security Principles
- Defense in Depth: Multiple layers of security; no single point of failure
- Least Privilege: Grant minimum permissions needed for the task
- Fail Secure: When things break, deny access rather than allowing it
- Separation of Concerns: Auth logic centralized in a dedicated service
- Audit Everything: Log all security-relevant events for forensic analysis
- Rotate Regularly: Keys, passwords, certificates should be rotated on schedule
- Encrypt Everything: Data at rest, in transit, and in use when possible