API Security & OAuth 2.0
Understand API authentication and authorization mechanisms, JWT security, and the OAuth 2.0 framework including Authorization Code Flow with PKCE.
The Concept
Securing network-accessible APIs requires balancing user convenience with rigid access control. At its foundation, security relies on distinguishing between authentication (verifying who a client is) and authorization (verifying what a client has permission to do).
Historically, applications used stateful session cookies, storing session identifiers in a database and comparing them against cookies sent in HTTP requests. In modern distributed architectures, APIs leverage stateless bearer tokens (specifically JSON Web Tokens, or JWT) to avoid database bottlenecks and support decoupled systems.
<svg viewBox="0 0 580 340" xmlns="http://www.w3.org/2000/svg" style="background-color: var(--color-surface-muted, #1f2428); border-radius: 0.75rem; border: 1px solid var(--color-border); padding: 1rem; width: 100%;">
<text x="290" y="20" fill="#88c0d0" font-family="sans-serif" font-size="14" font-weight="bold" text-anchor="middle">OAuth 2.0 Authorization Code Flow with PKCE</text>
<line x1="70" y1="70" x2="70" y2="300" stroke="#4c566a" stroke-width="1.5" stroke-dasharray="4 4"/>
<line x1="220" y1="70" x2="220" y2="300" stroke="#4c566a" stroke-width="1.5" stroke-dasharray="4 4"/>
<line x1="390" y1="70" x2="390" y2="300" stroke="#4c566a" stroke-width="1.5" stroke-dasharray="4 4"/>
<line x1="510" y1="70" x2="510" y2="300" stroke="#4c566a" stroke-width="1.5" stroke-dasharray="4 4"/>
<rect x="30" y="45" width="80" height="25" rx="4" fill="#2e3440" stroke="#88c0d0"/>
<text x="70" y="61" fill="#eceff4" font-family="sans-serif" font-size="10" text-anchor="middle">User</text>
<rect x="175" y="45" width="90" height="25" rx="4" fill="#2e3440" stroke="#88c0d0"/>
<text x="220" y="61" fill="#eceff4" font-family="sans-serif" font-size="10" text-anchor="middle">Client (SPA)</text>
<rect x="340" y="45" width="100" height="25" rx="4" fill="#2e3440" stroke="#88c0d0"/>
<text x="390" y="61" fill="#eceff4" font-family="sans-serif" font-size="10" text-anchor="middle">Auth Server</text>
<rect x="465" y="45" width="90" height="25" rx="4" fill="#2e3440" stroke="#88c0d0"/>
<text x="510" y="61" fill="#eceff4" font-family="sans-serif" font-size="10" text-anchor="middle">Resource API</text>
<path d="M 220 90 L 390 90" stroke="#d8dee9" stroke-width="1.2" marker-end="url(#arrow)"/>
<text x="305" y="84" fill="#d8dee9" font-family="sans-serif" font-size="9" text-anchor="middle">1. Auth Redirect + challenge</text>
<path d="M 70 120 L 390 120" stroke="#d8dee9" stroke-width="1.2" marker-end="url(#arrow)"/>
<text x="230" y="114" fill="#d8dee9" font-family="sans-serif" font-size="9" text-anchor="middle">2. Authenticate & Consent</text>
<path d="M 390 150 L 220 150" stroke="#d8dee9" stroke-width="1.2" marker-end="url(#arrow)"/>
<text x="305" y="144" fill="#d8dee9" font-family="sans-serif" font-size="9" text-anchor="middle">3. Auth Code (temporary)</text>
<path d="M 220 180 L 390 180" stroke="#a3be8c" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="305" y="174" fill="#a3be8c" font-family="sans-serif" font-size="9" text-anchor="middle">4. Exchange code + verifier</text>
<path d="M 390 210 L 220 210" stroke="#a3be8c" stroke-width="1.5" marker-end="url(#arrow-green)"/>
<text x="305" y="204" fill="#a3be8c" font-family="sans-serif" font-size="9" text-anchor="middle">5. Access Token (+ Refresh)</text>
<path d="M 220 240 L 510 240" stroke="#ebcb8b" stroke-width="1.2" marker-end="url(#arrow-yellow)"/>
<text x="365" y="234" fill="#ebcb8b" font-family="sans-serif" font-size="9" text-anchor="middle">6. Request + Bearer Token</text>
<path d="M 510 270 L 220 270" stroke="#ebcb8b" stroke-width="1.2" marker-end="url(#arrow-yellow)"/>
<text x="365" y="264" fill="#ebcb8b" font-family="sans-serif" font-size="9" text-anchor="middle">7. Protected Resource Data</text>
<defs>
<marker id="arrow" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L0,6 L6,3 z" fill="#d8dee9"/>
</marker>
<marker id="arrow-green" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L0,6 L6,3 z" fill="#a3be8c"/>
</marker>
<marker id="arrow-yellow" markerWidth="6" markerHeight="6" refX="5" refY="3" orient="auto">
<path d="M0,0 L0,6 L6,3 z" fill="#ebcb8b"/>
</marker>
</defs>
</svg>
Practical Analogy
Think of security credentials as airport boarding processes:
- Authentication is proving your identity at the check-in desk by presenting a passport. The agent verifies you are who you say you are and issues a boarding pass.
- Authorization is the boarding pass itself. The security agents and flight crew do not look up your registration records: they read the printed pass, which details your flight number, gate, and seating class. A first-class ticket authorizes entry to the VIP lounge, whereas an economy ticket denies it.
- A JSON Web Token is like a digital boarding pass with a security hologram (the signature). Gate agents inspect the signature to confirm it was generated by the airline and check the flight date to ensure it has not expired.
Stateful Sessions vs Stateless Tokens
In a stateful session architecture, the backend generates a unique session ID, saves it in a database or in-memory store (e.g. Redis), and returns it to the client inside a cookie. On subsequent requests, the server reads the cookie and queries the store to retrieve the user session. This approach makes invalidation simple, but it creates a central database bottleneck that hinders horizontal scaling.
Stateless token architectures encode the user state directly into a cryptographically signed token string. The server verifies the cryptographic signature without looking up the token in a database.
JWT Anatomy
A JWT is composed of three base64url-encoded parts separated by periods:
- Header: Declares the token type and the signing algorithm (e.g.
{"alg": "RS256", "typ": "JWT", "kid": "key-id-1"}). - Payload: Houses the claims, which are statements about the user and token metadata (e.g.
{"sub": "user_123", "iss": "auth.server.com", "exp": 1718131200}). - Signature: Created by signing the combined header and payload.
Symmetric vs Asymmetric Signatures
Symmetric signing (HS256) uses the same secret key to both generate and verify tokens. If a microservice needs to authorize requests, it must share the secret key, creating a security risk: if any service is compromised, an attacker can forge tokens for the entire system.
Asymmetric signing (RS256) uses a private key held by the Authorization Server to sign tokens, and a corresponding public key to verify them. Resource servers only need the public key to check the signature, meaning they can safely validate tokens without any ability to forge them.
The OAuth 2.0 Framework
OAuth 2.0 is an industry-standard authorization framework. It delegates access control, allowing third-party applications to obtain limited access to an HTTP service on behalf of a resource owner.
OAuth 2.0 defines four fundamental roles:
- Resource Owner: The user who grants access to a protected resource.
- Client: The application requesting access to the resource on behalf of the owner.
- Authorization Server: The server that authenticates the owner, obtains authorization, and issues access tokens.
- Resource Server (API): The server hosting the protected data, capable of accepting and validating access tokens.
Key OAuth 2.0 Flows
Different client types require different authorization flows:
- Authorization Code Flow with PKCE: Designed for mobile apps and Single Page Applications (SPAs) where a client secret cannot be safely hidden. The client generates a random code verifier and hashes it to create a code challenge. During redirection to the Authorization Server, the client sends the challenge. Once the user approves access, the Authorization Server returns a short-lived authorization code. The client then sends a direct POST request containing the authorization code and the raw
code_verifier. The Authorization Server hashes the verifier and validates it against the initial challenge before issuing anaccess_token, preventing interceptors from stealing the code. - Client Credentials Flow: Used for machine-to-machine communication where no user is present (e.g. an internal billing service requesting database synchronizations from a user service). The client sends its secure
client_idandclient_secretdirectly to the Authorization Server to receive an access token.
Token Lifecycle & Threat Mitigation
Because stateless tokens cannot be easily deleted by the server, managing their lifecycle is critical:
- Access Token Expiration: Access tokens are short-lived (e.g. 15 minutes) to minimize the window of opportunity if a token is stolen.
- Refresh Tokens: When an access token expires, the client uses a long-lived refresh token (e.g. 30 days) to request a new access token from the Authorization Server without prompting the user to log in again.
- Refresh Token Rotation: Each time a client uses a refresh token, the Authorization Server invalidates it and returns a new refresh token. If an attacker steals a refresh token, they will trigger a double-use alarm at the server when the victim attempts to refresh, prompting the server to immediately revoke all active tokens in that chain.
- JWKS (JSON Web Key Sets): The Authorization Server publishes its public verification keys on a public HTTP endpoint as a JWK Set. Resource APIs download and cache these keys, allowing them to dynamically verify asymmetric token signatures and handle automatic key rotation without downtime.
Common API Threats
Standard authentication does not prevent application-level authorization bugs. Developers must protect APIs against the OWASP API Security Top 10 vulnerabilities:
- BOLA (Broken Object Level Authorization): Occurs when an API endpoint exposes an identifier (e.g.
/api/v1/accounts/993) and fails to verify if the authenticated user has permission to view that specific record. An attacker can iterate the ID parameter to view other users' private accounts. - BFLA (Broken Function Level Authorization): Occurs when administrative endpoints fail to verify roles, permitting regular users to call privileged operations (e.g. a standard user sending a POST request to
/api/v1/admin/delete-user).
Further Reading
- RFC 6749: The OAuth 2.0 Authorization Framework — The core specifications for OAuth flows
- RFC 7636: Proof Key for Code Exchange (PKCE) — The technical specifications for PKCE extension
- RFC 7519: JSON Web Token (JWT) — Standard definition and structure of JWTs
- OWASP API Security Top 10 — Common security vulnerabilities and mitigation guidelines
Prerequisites
Code Examples
Continue learning
ACID & Isolation Levels
Deep dive into database transaction guarantees, isolation levels, concurrency anomalies like write skew, and control mechanisms such as MVCC, 2PL, and SSI.
API Gateways
Understand the API Gateway pattern as the central ingress point for microservices, handling routing, auth, rate limiting, and protocol translation.
API Versioning Strategies
Learn how to manage breaking API updates using URI paths, query params, headers, and media types, while coordinating schema translation and deprecations.