Two topics that mark the boundary between knowing security features and understanding security architecture.
OAuth 2.0 solves delegated access — letting one application act on a user's behalf without holding their password. Zero trust rejects the assumption that being inside the network means being trusted.
Both appear constantly in senior interviews, and both are commonly described inaccurately.
OAuth 2.0: the four roles
Precision here prevents most confusion. Every OAuth conversation involves four parties:
- Resource owner — the user who owns the data.
- Client — the application requesting access.
- Authorization server — issues tokens after the user consents.
- Resource server — holds the data and accepts tokens.
The scenario: a photo-printing site wants your Google Photos. You are the resource owner, the printing site is the client, Google's login is the authorization server, and the Photos API is the resource server.
The point is that the printing site never sees your Google password. It receives a token that is scoped, expiring and independently revocable.
The authorization code flow
The main flow, and the one to be able to draw.
Diagram, as text
- User leads to Client app (1. click connect)
- Client app leads to Authorization server (2. redirect to auth server)
- Authorization server leads to Authorization server (3. user logs in, consents)
- Authorization server leads to Client app (4. redirect back with CODE)
- Client app leads to Authorization server (5. code + client secret)
- Authorization server leads to Client app (6. access token)
- Client app leads to Resource server (7. token)
Step 4 returns a short-lived authorization code, not a token. The client then exchanges that code for a token in a back-channel request — server to server, carrying its client secret.
That indirection is deliberate, and worth understanding rather than memorising. The redirect in step 4 travels through the user's browser, where it appears in URLs, browser history and referrer headers. A token there would be exposed. A code is useless without the client secret, and it is single-use and expires in seconds.
PKCE, and why it exists
The back-channel exchange assumes the client can keep a secret. Mobile apps and single-page applications cannot — anyone can decompile an app or read JavaScript, so a client secret shipped to users is not a secret.
Without one, an attacker who intercepts the authorization code can redeem it themselves.
PKCE (Proof Key for Code Exchange) fixes this without a stored secret:
- The client generates a random code verifier and sends its hash — the challenge — with the initial request.
- When exchanging the code, it sends the original verifier.
- The authorization server hashes it and compares.
An intercepted code is now useless, because the attacker does not have the verifier, and cannot derive it from the hash.
PKCE is now recommended for all clients, including confidential ones. If you take one implementation detail from this lesson, take that.
Scopes and consent
Tokens are scoped: photos.read rather than "everything". This is least privilege applied to delegation, and it is what makes revocation meaningful — the user can withdraw one application's access without changing their password or affecting anything else.
Request the narrowest scope that works. Applications asking for broad permissions they do not need get refused by users and flagged in reviews.
OAuth is not authentication
The most common misunderstanding, worth repeating from the authentication lesson.
OAuth answers "may this app access that resource?". It does not establish who the user is — an access token proves someone authorised access, not the identity of the bearer.
OpenID Connect adds that: an ID token, a signed JWT with verified identity claims, plus a standard /userinfo endpoint. "Sign in with Google" is OIDC.
Using a raw OAuth access token as proof of identity is a real vulnerability — a token obtained for one application can be replayed to another that naively accepts it. This is the confused deputy problem, and OIDC's ID token exists precisely because of it.
Zero trust
The traditional model is a perimeter: a hardened boundary with a firewall, and a trusted interior. Get inside and you are largely free.
The assumption fails for several reasons. Cloud services have no clean perimeter. Employees work remotely. Third-party integrations reach in. And most importantly, one compromised machine inside gives an attacker the trusted interior — which is exactly how large breaches escalate from a single phishing email to total compromise.
Zero trust replaces the perimeter with a principle:
Never trust, always verify. Trust is not granted by network location.
What it changes in practice
Every request is authenticated and authorised, including service-to-service calls. An internal service does not accept a request merely because it arrived on the internal network.
Encrypt internal traffic. This is the point deferred from the security basics lesson: terminating TLS at the load balancer and sending plaintext internally assumes a trusted network. Zero trust uses mutual TLS, where both sides present certificates, so each verifies the other's identity.
Identity for services, not just users. Each service gets a cryptographic identity — a certificate — and short-lived credentials, so a leaked one expires quickly.
Least privilege everywhere. A service may read the orders table and nothing else. If it is compromised, the blast radius is what it could legitimately reach.
Assume breach. Design so a single compromised component is contained: segment networks, keep credentials narrow, log everything for detection.
Diagram, as text
- Firewall leads to Trusted interior
- Service A leads to Service B (mTLS + authz)
- Service B leads to Service C (mTLS + authz)
The cost
Zero trust is genuinely more work: certificate management and rotation for every service, latency from verification on every hop, and real operational complexity. Service meshes exist largely to make it tractable by handling mTLS and policy outside application code.
Say this in an interview. Presenting zero trust as free is as unconvincing as presenting microservices that way.
Check your understanding
Common mistakes
Treating an OAuth access token as identity. Use OIDC's ID token; accepting an access token as proof of who someone is invites token replay.
Requesting excessive scopes. Users decline, and a compromise is worse than it needed to be.
Not validating the redirect URI. An authorization server that redirects anywhere lets an attacker steal codes. Whitelist exactly.
Ignoring the state parameter. It ties the callback to the request that started it, and omitting it enables CSRF on the authorization flow.
Long-lived access tokens. Same reasoning as JWTs — keep them short and use refresh tokens.
"Zero trust" as a product purchase. It is an architectural principle, not something you install.
Practice
A public mobile app cannot keep a client secret. Explain the problem this creates for OAuth, and how PKCE resolves it.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Ask what stops an attacker who intercepts the authorization code from redeeming it.
Show a strong answer
The problem. The authorization code flow relies on two things being required at the token endpoint: the code, and the client secret. A mobile app is distributed to users, so anything embedded in it can be extracted by decompiling the binary — the secret is public by definition. Remove the secret and the code alone is sufficient to obtain a token.
That matters because the code arrives via a redirect to a custom URL scheme, and on some platforms another application can register the same scheme and receive it. An attacker who intercepts the redirect can redeem the code and obtain the user's tokens.
How PKCE resolves it — by replacing a static shared secret with a per-request one.
- Before starting, the app generates a random code verifier and sends only its hash (the code challenge) with the authorization request.
- The authorization server stores the challenge alongside the issued code.
- When redeeming, the app sends the verifier. The server hashes it and checks it matches the challenge it stored.
An attacker who intercepts the code does not have the verifier — it never left the app — and cannot derive it from the challenge, because the challenge is a hash. The code becomes useless to anyone except the application instance that began the flow.
Two details that matter: use the hashed method rather than sending the verifier in plaintext as the challenge, which defeats the purpose; and PKCE is now recommended for confidential clients too, not just public ones, because it also defends against code injection where a secret would not.
Your company runs 40 internal services on a private network with no authentication between them, because 'the network is trusted'. Design the migration to per-request authentication without a flag day.
Write your own answer first, on paper or in a note. Then compare.
Show a hint
Every step must leave both authenticated and unauthenticated callers working.
Show a strong answer
The constraint is that 40 services cannot change simultaneously, so every step must tolerate a mixed population.
- Give every service an identity first. Issue a workload credential — a certificate or a signed token — from a central authority, delivered automatically at startup and rotated frequently. Nothing enforces anything yet; this step only makes identity available, and it is the largest piece of work.
- Start presenting credentials, without requiring them. Callers attach their identity to every request. Receivers log what they see and ignore its absence. After this step you have data: which callers are ready, which are not, and — usually a surprise — which callers nobody knew existed.
- Enforce per service, in dependency order. Pick the service with the fewest callers, verify every caller is presenting a valid identity in the logs, then switch it to reject unauthenticated requests. Repeat outward. Each switch is small and independently revertible.
- Add authorisation once authentication is universal. Move from "is this a known service?" to "is this service permitted to call this endpoint?" This is a second pass, and attempting it simultaneously with step 3 doubles the risk of each cutover.
What makes this safe: at every point, the system works for both populations, and each enforcement decision affects one service.
The two things I would insist on:
- Never enforce without the observation period. The log data in step 2 is what turns a cutover from a gamble into a check.
- Keep it automatic. If a certificate must be renewed by a human, the migration succeeds and then causes an outage three months later. Rotation is the part that determines whether this survives.
Key takeaways
- OAuth 2.0 has four roles, and its purpose is delegated access without sharing a password — producing tokens that are scoped, expiring and revocable.
- The authorization code flow returns a code through the browser and exchanges it server-to-server, because a token in a redirect is exposed.
- PKCE removes the need for a client secret and is now recommended for every client. The implicit flow is deprecated.
- OAuth is authorisation; OIDC adds authentication via an ID token. Using an access token as identity is a real vulnerability.
- Zero trust discards network location as a basis for trust — every request authenticated, internal traffic under mTLS, services given identities, least privilege throughout.
- It assumes breach is inevitable and optimises for containment rather than prevention.
- It is not free: certificate management, per-hop verification latency and operational complexity are the price.
Further reading
- RFC 6749, The OAuth 2.0 Authorization Framework, and RFC 7636, Proof Key for Code Exchange (PKCE).
- RFC 9700, Best Current Practice for OAuth 2.0 Security — including PKCE for all clients.
- NIST Special Publication 800-207, Zero Trust Architecture.
- Google, "BeyondCorp: A New Approach to Enterprise Security" (USENIX ;login:, 2014).
- Evan Gilman and Doug Barth, Zero Trust Networks (O'Reilly, 2017) — workload identity and mutual authentication.