Authentication & Authorization
Session 4.5 · ~5 min read
Two Different Questions
Security in software systems starts with two fundamental questions. Authentication (AuthN) asks: who are you? Authorization (AuthZ) asks: what are you allowed to do? These are separate concerns, implemented by separate mechanisms, and they must never be confused.
Authentication verifies identity. When you type your username and password, the system checks whether those credentials match a known user. If they do, the system knows who you are. Authentication is a binary outcome: either the credentials are valid or they are not.
Authorization determines access. Once the system knows who you are, it must decide what you can access. Can you read this document? Can you delete this record? Can you access data belonging to a different tenant? Authorization is contextual and granular. The same authenticated user might have full access in one area and no access in another.
Authentication asks "who are you?" Authorization asks "what can you do?" Never confuse the two. A valid login does not mean unlimited access. A permission check does not verify identity.
OAuth 2.0: Delegated Authorization
OAuth 2.0 is an authorization framework, defined in RFC 6749, that allows a user to grant a third-party application limited access to their resources without sharing their password. When you click "Sign in with Google" on a third-party site, OAuth 2.0 is handling the flow.
The most widely used OAuth 2.0 flow is the Authorization Code Flow. It involves four parties: the user (resource owner), the client application, the authorization server, and the resource server.
(client_id, redirect_uri, scope, state) AS->>U: Show login + consent screen U->>AS: Enter credentials, grant consent AS->>C: Redirect to callback with authorization code C->>AS: POST /token
(code, client_id, client_secret) AS->>C: Return access_token (+ refresh_token, id_token) C->>RS: GET /api/resource
(Authorization: Bearer access_token) RS->>C: Return protected resource C->>U: Display resource
The critical security property of this flow is that the user's credentials are never shared with the client application. The client receives an authorization code, which it exchanges for an access token using a server-to-server call that includes the client's own secret. The access token is scoped: it grants access only to the specific resources the user consented to, not to everything.
For public clients (single-page applications, mobile apps) that cannot securely store a client secret, the Authorization Code Flow is extended with PKCE (Proof Key for Code Exchange, pronounced "pixie"). PKCE prevents authorization code interception attacks by requiring the client to prove it was the one that initiated the flow.
OpenID Connect: Authentication on Top of OAuth
OAuth 2.0 by itself is an authorization protocol. It tells the client what the user has allowed, but it does not reliably tell the client who the user is. OpenID Connect (OIDC) adds an authentication layer on top of OAuth 2.0.
The key addition is the ID token, a JWT that contains claims about the authenticated user: their unique identifier, email, name, and when the authentication occurred. The client can decode this token to learn who the user is without making additional API calls.
JSON Web Tokens (JWT)
A JWT is a compact, URL-safe token format defined in RFC 7519. It consists of three parts separated by dots: a header (algorithm and token type), a payload (the claims), and a signature.
The payload contains standard claims and custom claims. Standard claims are registered in the IANA JWT Claims Registry.
| Claim | Name | Purpose | Example Value |
|---|---|---|---|
sub |
Subject | Unique identifier for the user | "user-12345" |
iss |
Issuer | Who issued the token | "https://auth.example.com" |
aud |
Audience | Intended recipient of the token | "api.example.com" |
exp |
Expiration | When the token expires (Unix timestamp) | 1775145600 |
iat |
Issued At | When the token was created | 1775142000 |
scope |
Scope | Permissions granted to this token | "read:profile write:settings" |
The signature ensures integrity. If anyone modifies the payload (for example, changing their user ID to an admin's ID), the signature will not match and the token will be rejected. JWTs can be signed with HMAC (shared secret) or RSA/ECDSA (asymmetric keys). Asymmetric signing is preferred because the resource server only needs the public key to verify tokens, not the private signing key.
Multi-Factor Authentication (MFA)
Passwords alone are weak authentication. They can be guessed, phished, leaked in breaches, or reused across sites. Multi-factor authentication requires the user to prove their identity using two or more independent factors from different categories: something you know (password), something you have (phone, hardware key), and something you are (fingerprint, face).
Common MFA methods include time-based one-time passwords (TOTP, generated by apps like Google Authenticator), SMS codes (less secure due to SIM-swapping attacks), push notifications to an authenticator app, and hardware security keys (FIDO2/WebAuthn, the strongest option). For high-security systems, hardware keys provide the best protection because they are resistant to phishing. The user must physically possess and activate the key.
RBAC vs. ABAC
Once a user is authenticated, the system must decide what they can access. The two dominant models for this decision are Role-Based Access Control (RBAC) and Attribute-Based Access Control (ABAC).
| Dimension | RBAC | ABAC |
|---|---|---|
| Access based on | Assigned roles (admin, editor, viewer) | Attributes of user, resource, and environment |
| Granularity | Coarse (role-level) | Fine-grained (attribute combinations) |
| Policy example | "Editors can update articles" | "Users in the EU can access EU data during business hours" |
| Scalability | Role explosion in complex systems | Scales well with dynamic policies |
| Implementation complexity | Low to medium | High |
| Audit clarity | Easy to audit (who has which role) | Harder (policies are rules, not lists) |
| Best for | Applications with clear role hierarchies | Applications needing contextual access decisions |
RBAC assigns permissions to roles, then assigns roles to users. A user with the "editor" role can edit content. A user with the "admin" role can manage users. RBAC is straightforward and works well when the access model maps cleanly to organizational roles. The problem appears when you need exceptions: "editors can edit articles, but only articles in their department, and only during business hours." Each exception requires a new role, leading to role explosion.
ABAC evaluates access based on attributes of the user (department, clearance level), the resource (classification, owner), the action (read, write, delete), and the environment (time of day, IP address, device type). ABAC policies are rules, not lists. They handle complex access logic without role explosion, but they are harder to implement and harder to reason about.
Many production systems use both. RBAC handles the common cases (admin, editor, viewer). ABAC handles the exceptions and contextual rules that RBAC cannot express cleanly.
IAM in Cloud Environments
Cloud providers implement authorization through Identity and Access Management (IAM) services. AWS IAM, Azure RBAC, and Google Cloud IAM all follow the same core model: identities (users, groups, service accounts) are assigned policies that grant or deny specific actions on specific resources.
The principle of least privilege applies everywhere: every identity should have only the permissions it needs to perform its function, and nothing more. This limits the blast radius of a compromised credential. A database backup service should not have permission to delete databases. A read-only monitoring service should not have write access.
Systems Thinking Lens
Authentication and authorization form a dependency chain. Every downstream service trusts the token issued by the authentication layer. If that layer is compromised, every service that trusts it is compromised. This is a leverage point (Session 0.6) in the negative sense: a single vulnerability at the auth layer cascades through the entire system.
The feedback loop in access control is also worth noting. Overly restrictive permissions generate friction. Users request exceptions. Exceptions accumulate into overly permissive configurations. Security incidents trigger a lockdown. The cycle repeats. Good access control design anticipates the common access patterns and builds them into the model, reducing the pressure for exceptions.
Further Reading
- RFC 6749: The OAuth 2.0 Authorization Framework. The specification that defines OAuth 2.0 flows, token types, and security considerations.
- OpenID Connect Core 1.0. The specification for OIDC, including ID token format and standard claims.
- Auth0: Authorization Code Flow. A practical, well-illustrated guide to implementing the authorization code flow.
- Okta: OAuth 2.0 and OpenID Connect Overview. Clear explanation of how OAuth 2.0 and OIDC work together.
- NIST SP 800-162: Guide to ABAC. The formal definition and implementation guidance for attribute-based access control.
Assignment
You are designing the authentication and authorization system for a multi-tenant SaaS application. Tenants are separate companies. Users belong to one tenant and should never access another tenant's data.
- Design the login flow. The user opens the app, enters credentials, and receives a token. Draw a sequence diagram showing each step, including the authorization server, the client application, and the API.
- Design the JWT payload. What claims do you include to ensure the API can enforce tenant isolation? Write out the exact JSON payload, including standard claims (
sub,iss,exp,aud) and any custom claims you need (tenant ID, roles, permissions). - Choose an access control model. Should you use RBAC, ABAC, or a combination? Justify your choice based on the multi-tenant requirement. Provide an example policy for each model.
- A user with the "admin" role in Tenant A makes an API call to access data in Tenant B. Describe exactly how the system detects and blocks this request. At which layer does the check happen?