Skip to content
intermediate Phase 13 · Security Architecture and Zero Trust

Identity and Access Architecture

Choose federation, OAuth flows, and policy models at the system level.

55m
0 problems
Topic Progress 0%

Twelve Services, One Login: Centralizing Authentication Behind an IdP

Twelve teams each shipped their own login page. The result is twelve password stores, twelve implementations of lockout policy, and — after one weak service is breached — a credential-stuffing wave against the other eleven because people reuse passwords. Offboarding an employee means twelve tickets to twelve teams, and nobody can answer 'which systems can this person still access?' without a week of archaeology. This is the failure mode that identity architecture exists to prevent, and the fix is structural: one authentication implementation, one place to enforce MFA, lockout, and offboarding — the identity provider.

The IdP pattern is: applications redirect authentication to the provider, receive tokens proving who the user is, and never see a password. Protocol choice is a design-review question with a short answer:

Protocol Artifact Best for Notes
OIDC JWT ID + access tokens modern apps, mobile SPAs, APIs REST/JSON; the default choice for new systems
SAML XML assertion enterprise SaaS federation Older, still dominant for corporate app SSO
LDAP/AD bind directory query legacy apps inside one directory On-prem gravity well; prefer fronting it with the IdP

Two lifecycle mechanisms turn the IdP from a login page into an identity architecture. The first is provisioning: SCIM or the provider's directory sync pushes joiner-mover-leaver events from HR to every integrated application, so an engineer who changes teams loses the payments scope automatically instead of when an access reviewer recalls it two quarters later. The second is session architecture: the IdP owns the single sign-on session (hours), while each service holds its own short access token (minutes) plus a rotating refresh token. One logout at the IdP ends the SSO session; token lifetimes decide how long a hijacked browser tab stays useful. Designs that conflate the two either never log anyone out or re-prompt users every ten minutes.

Federation layers on top: the corporate directory (AD) becomes one upstream source, partner IdPs another, social logins external identities. Two hazards are worth knowing cold. First, account linking by unverified email is an account-takeover vector — an attacker registers at a social provider with a victim's address and gets silently linked to the victim's account; require verified emails and explicit, user-confirmed linking. Second, step-up authentication: the IdP is where you demand a hardware-backed factor for sensitive actions (prod tooling, bulk data export) rather than treating one login's assurance level as permanent.

Why not just share an auth library across the twelve services? It fixes the hashing code and leaves the lifecycle broken: password reset flows, lockout consistency, session revocation, the audit trail, and — the one that bites during incidents — 'revoke this user everywhere, now.' A central provider gives you one session-revocation point and one audit trail to query — and it changes the incident playbook: killing one IdP-side session propagates to every service within one token TTL, turning 'we think this user's session is compromised' from a twelve-ticket scramble into a single revoke. Then the follow-up every senior interviewer reaches for: centralization concentrates risk, so what protects the IdP itself? The answers are hardware MFA for IdP admins, a break-glass account with sealed credentials and alarmed usage, redundancy so the IdP is not your single largest availability dependency, and scoped tokens so that even a total IdP compromise leaves every consumer enforcing its own audience and scope checks. That last point is the bridge to delegation: tokens, not identity-provider omnipotence, are what your services actually trust.

Delegation Without Passwords: Choosing the Right OAuth 2.0 Flow

A logistics partner wants to pull order status through your API. The naive ask is for a shared API key or, worse, a test user's password. OAuth 2.0 exists to refuse that ask precisely: delegation means the third party acts with scoped permissions on your behalf, never with your credentials. The architecture skill is matching scenario to flow, because most real vulnerabilities in OAuth deployments are flow mix-ups.

Flow Who acts Use it for
Authorization code + PKCE End user via an app Third-party and first-party apps; the only browser flow to ship
Client credentials Machine, no user Service-to-service calls (your batch job → ML API)
Device authorization User on a second device TVs, CLIs, anything without a browser
Refresh token rotation Any long session Long-lived access without long-lived access tokens
Implicit / resource-owner password — Deprecated; removed in OAuth 2.1 — never design them in

PKCE deserves the interview minute it usually gets: the client generates a random code_verifier, sends its SHA-256 code_challenge at the authorize endpoint, and presents the verifier at the token endpoint. An attacker who intercepts the authorization code (via a malicious app claiming the redirect scheme, or a proxy) cannot redeem it without the verifier, which kills the classic code-interception attack even for public clients with no client secret.

Redirect URI handling is the other classic hole: an attacker who can register or manipulate a redirect target captures the code — and with it the tokens — via an open redirect. Pin exact redirect URIs, no wildcards and no prefix matching, and treat client registrations as reviewed artifacts. Consent phishing deserves the same attention: a convincingly branded app that asks for orders:read collects user consent and then harvests data for months. Keep per-integration scopes minimal, name your organization on the consent screen, and require admin approval rather than end-user consent alone for high-value scopes like refund execution.

Scopes and audiences are where access is actually bounded. A scope answers what (orders:read, orders:refund); the audience (aud) answers which API the token is for. Both are encoded in the JWT, and validating aud is as important as verifying the signature — an access token minted for your logging pipeline must be rejected by the payments API, or every downstream service becomes a confused deputy minting access to the others. The full validation checklist your services must implement: signature via the IdP's JWKS (matched by kid), iss, aud, exp with bounded clock skew, and the scopes required for this specific endpoint.

Lifetimes are the final trade-off, and it is a real one. A JWT is verified offline, so there is no moment where revocation propagates — the token stays valid until it expires. The two defenses are introspection (a live call per request, true revocation, added latency and an availability dependency) and short TTLs (5–15 minute access tokens plus rotating refresh tokens, where presenting a previously-used refresh token revokes the whole family). Most architectures pick short TTLs with rotation and reserve introspection for high-value admin scopes. One structural pattern on top: a central token service that mints downstream tokens (token exchange) keeps the partner-facing contract stable when internal services split or merge, instead of every partner integrating against every internal API's auth quirks. Note the interaction with Zero Trust Architecture: per-request verification of short-lived tokens is exactly the mechanism that makes stolen tokens die in minutes instead of hours.

Privilege Escalation Paths: Reading Every Policy Like an Attacker

The audit report says your CI role is clean: 'read-only, one S3 bucket.' Read the policy yourself and keep going past the first statement. Here is the shape that has owned more cloud accounts than any CVE:

{
  "Effect": "Allow",
  "Action": ["s3:GetObject", "lambda:UpdateFunctionCode", "iam:PassRole"],
  "Resource": [
    "arn:aws:s3:::ci-artifacts/*",
    "arn:aws:lambda:us-east-1:123456789012:function:ci-*",
    "arn:aws:iam::123456789012:role/ci-*"
  ]
}

Walk the path an attacker walks. lambda:UpdateFunctionCode on any ci-* function lets them upload malicious code. iam:PassRole — with no condition — lets them attach an admin role to that function at deploy time. Trigger the function, and their code executes as admin. The 'read-only CI role' was two API calls away from full account takeover. The escalation catalogue every architect should recognize on sight: iam:PassRole unconstrained; iam:CreatePolicyVersion / iam:AttachUserPolicy (self-escalation); iam:CreateAccessKey on any principal; sts:AssumeRole against a role whose trust policy says Principal: {AWS: account-root} (trusting the whole account); and lambda or glue functions that can be re-pointed at privileged roles. Any allow-listing IAM or STS action is a candidate admin grant — treat policy statements mentioning iam:* or sts:AssumeRole the way you would treat AdministratorAccess until proven otherwise.

The close for this exact policy narrows PassRole to one role and one service, and gives the function's own role a permission boundary so anything the CI role creates is capped regardless of the policy attached later:

{
  "Effect": "Allow",
  "Action": "iam:PassRole",
  "Resource": "arn:aws:iam::123456789012:role/ci-runner",
  "Condition": {
    "StringEquals": { "iam:PassedToService": "lambda.amazonaws.com" }
  }
}

The closes are conditions, boundaries, and fences. Scope PassRole with iam:PassedToService and an exact role ARN; replace account-root trust policies with named principals; attach permission boundaries to every role the CI role itself can create, so its creations are capped regardless of what policy they carry; and put service control policies at the organization level as the outer fence that even root-ish principals cannot cross. The SCP takes the shape of a deny on iam:CreatePolicyVersion and iam:AttachUserPolicy outside a dedicated administration path, applied to every member account — an organization-level deny survives even a compromised account root, because nothing inside the account can override it. When you present an escalation path in an interview, the follow-up is always the fence, and 'an SCP denies it at the org level' is the answer that ends the discussion.

The architectural counterpart to hand-written policies is the policy engine: authorization logic hard-coded across a dozen services drifts — service A checks tenant scoping, service B checks roles, service C checks neither — while a central engine (OPA, Cedar, or a managed service) evaluates attributes — subject role, resource owner, tenant, environment — at request time, giving one decision point, one audit trail, and the ability to change policy without redeploying consumers. Treat policies as data: reviewed in pull requests, tested with policy simulation in CI, and shrunk with access-advisor usage data instead of guessing.

The interview payoff question is usually 'roll out least privilege without breaking production.' The answer with scar tissue: run in simulate/audit mode first, group principals by observed usage, tighten in waves with an owner per wave, keep an alarmed break-glass role for the flow you missed — and always, always review escalation paths before declaring the rollout done, because a least-privilege policy with one wildcard on iam:PassRole is not least privilege at all. Policy simulation in CI is what makes the waves safe: every proposed policy change runs against recorded production actions from the last week, and the diff shows exactly which real requests would newly fail before any human feels it.

Quiz

1. A partner app needs to pull order status on behalf of an authenticated customer. Which OAuth 2.0 design is correct?

Question 1 options

2. A 15-minute access token was stolen from a compromised service. What is the practical revocation story for a JWT-based design without introspection?

Question 2 options

3. A CI role holds s3:GetObject on one bucket, lambda:UpdateFunctionCode on ci-* functions, and iam:PassRole on ci-* roles with no condition. What is the escalation risk?

Question 3 options

4. Your services each implement their own role-checking logic and they disagree on tenant scoping. What is the architectural fix a senior architect proposes?

Question 4 options

Flashcards

Question

Why centralize authentication behind an identity provider instead of sharing an auth library across services?

Answer

A library fixes hashing code but not the lifecycle: password reset, lockout policy, session revocation, audit trails, and offboarding. A central IdP is one place to enforce all of them and one audit trail to query — and one place to defend, which is why IdP admins need hardware MFA and a sealed break-glass account.

Question

Which OAuth 2.0 flows are appropriate for (a) a third-party app acting for a user and (b) service-to-service calls, and what replaces the implicit flow?

Answer

(a) Authorization code with PKCE; (b) client credentials when no user is in the loop. The implicit flow is removed in OAuth 2.1; PKCE (code_verifier/code_challenge) protects the code from interception and covers public clients that cannot hold a secret.

Question

What must a service validate in a JWT, and why is `aud` as important as the signature?

Answer

Signature via the IdP JWKS (matched by kid), iss, aud, exp with bounded clock skew, and required scopes. Audience binds the token to one API: without checking it, a token minted for a low-value API is replayed against a high-value one, turning every service into a confused deputy.

Question

Name four classic cloud privilege-escalation primitives and the primary mitigation for iam:PassRole.

Answer

iam:PassRole (unconstrained), sts:AssumeRole against an account-root trust policy, iam:CreatePolicyVersion/AttachUserPolicy on self, and re-pointable lambda/glue functions running privileged roles. Mitigate PassRole with iam:PassedToService plus an exact role ARN condition, and attach permission boundaries to creatable roles.

Question

Centralized policy engine vs shared authorization library vs gateway-only checks — what does the engine buy you?

Answer

One decision point, one audit trail, attribute evaluation per request (role, resource owner, tenant, env), and policy changes without redeploying consumers. Libraries drift across teams; gateway-only checks vanish on direct service calls.

Revision Notes

Key Takeaways

  • 1. One IdP means one implementation of MFA, lockout, revocation, and offboarding — and one crown-jewel system to harden accordingly
  • 2. OAuth delegation hands out scoped, revocable authority instead of credentials; flow mix-ups, not crypto, are the usual vulnerability
  • 3. Validate audience as strictly as signature, or every service becomes a confused deputy for the others' tokens
  • 4. Short access TTLs plus refresh rotation are the practical revocation answer for offline-verified JWTs
  • 5. Any policy statement touching iam:* or sts:AssumeRole is a potential admin grant — review escalation paths before calling least privilege done

Interview Tips

  • • "How would you add SSO to twelve services?" — redirect auth to one IdP, tokens to services, step-up for sensitive actions; then immediately name the concentration risk and its mitigations (admin MFA, break-glass, scoped tokens)
  • • "Walk me through OAuth for a third-party integration" — authorization code + PKCE, scopes and audience, short-lived access plus rotating refresh; explicitly reject implicit and password grants with the reason
  • • "How do you secure a multi-tenant SaaS API?" — object-level authorization per request, tenant as a policy attribute evaluated centrally, deny by default; name per-tenant scoping as the first thing hardcoded checks get wrong
  • • "Our CI role looks read-only — is it safe?" — trace escalation: PassRole + function update = admin; ask for conditions, boundaries, and trust-policy review before trusting the label

Cheat Sheet

Identity and Access Architecture

Centralize auth: IdP via OIDC (new apps), SAML (enterprise SaaS), LDAP only behind the IdP. Federation: AD upstream, partner IdPs, social logins. Hazards: email-only account linking (verify + explicit link), one assurance level forever (use step-up MFA).

OAuth flow picker:

Scenario Flow
User via app / third party Authorization code + PKCE
Machine to machine Client credentials
No-browser device Device authorization
Long session Refresh token rotation (reuse ⇒ revoke family)
Never Implicit, resource-owner password (gone in OAuth 2.1)

JWT checklist: signature via JWKS by kid, iss, aud, exp + clock skew, scopes per endpoint. Scopes = what; audience = which API. Revocation = short TTLs (5–15 min) + rotation; introspection only for high-value scopes.

Escalation red flags: iam:PassRole unconstrained (fix: iam:PassedToService + exact ARN), sts:AssumeRole with account-root trust, iam:CreatePolicyVersion/AttachUserPolicy/CreateAccessKey on self, re-pointable lambda/glue. Boundaries cap created roles; SCPs fence the org.

Authorization rollout: policy engine (OPA/Cedar/managed) as one decision point; policies as data, simulated in CI; tighten in waves from access-advisor data with an alarmed break-glass role.

Continue Learning