Skip to main content
  1. System Design Components/

OAuth2 / OIDC Authorization Server

OAuth2 / OIDC Authorization Server #

This note models an OAuth2/OIDC authorization server where clients initiate authorization flows, users authenticate and grant consent, the server issues authorization codes and tokens, and refresh, revocation, introspection, and key rotation are handled safely at scale.


Step 1 - Normalize #

Assume the baseline prompt is:

  • design an OAuth2 / OIDC authorization server
  • users authenticate and authorize client apps
  • server issues authorization codes, access tokens, ID tokens, and refresh tokens
  • clients redeem, refresh, revoke, and introspect tokens
  • consent, scopes, client trust, and signing keys matter
  • system scales across many tenants and apps

Normalize into state-affecting paths.

RequirementActorOperationState touchedPriority
Client starts authorization flowClientappend eventS1
create target
AuthorizationRequest
C1
Service authenticates user and verifies policySystemstate transitionS1
update target
AuthorizationFlowState
C1
User grants or denies consentClientstate transitionS1
update target
ConsentGrantState
C1
Service issues authorization codeSystemappend eventS1
create target
AuthorizationCode
C1
Client exchanges code for tokensClientstate transitionS1
update target
TokenGrantState
C1
Service issues access / ID / refresh tokensSystemappend eventS1
create target
IssuedToken
C1
Client refreshes tokenClientstate transitionS1
update target
TokenGrantState
C1
Client or admin revokes token / grantClientstate transitionS1
update target
TokenGrantState
C1
Client introspects token / userinfo / JWKSClientread sourceS1
read source target
TokenGrantState
R1
Admin updates client config / redirect URIs / scopesAdminoverwrite stateS1
update target
ClientConfig
C1
Admin updates auth policyAdminoverwrite stateS1
update target
AuthorizationPolicy
C1
System rotates signing keysSystemstate transitionS1
update target
SigningKeyState
C1
User reads consent/session activityClientread projectionS1
read projection target
AuthorizationActivityView
R2
System routes tenant/shard to current ownerSystemread sourceS1
read source target
PartitionMap
C1
System reassigns shard ownership after node failureSystemstate transitionS1
update target
PartitionOwnership
C1

Notes on normalization #

Important choices:

  • auth flow start is append event
    • authorization intent is an immutable interaction fact
  • auth/consent progression is state transition
    • current flow and grant lifecycle evolve
  • authorization code and token issuance are append event
    • each credential issuance is a fact
  • code exchange, refresh, and revocation change current grant state
  • client config and policy are current-value control state
  • key rotation is explicit because signed token validity depends on it

This system is a hybrid of:

  • delegated authorization flow
  • grant/session lifecycle state
  • signed token issuance

Step 2 - Critical Path Selection #

RequirementPriority classWhy
Start authorization flowC1flow intent and redirect correlation must be preserved
Authenticate user / verify policyC1wrong authorization decision is a security failure
Grant or deny consentC1consent correctness affects delegated access
Issue authorization codeC1code issuance is core protocol output
Exchange code for tokensC1token issuance must be bound to current valid code/grant
Refresh tokenC1refresh semantics determine long-lived access correctness
Revoke token / grantC1revocation correctness affects security
Introspect token / userinfo / JWKSR1core serving path
Update client config / policyC1future auth decisions and redirect validation depend on current config
Rotate signing keysC1key lifecycle controls token verification trust
Route to shard ownerC1wrong routing can split grant/session truth
Reassign shard ownershipC1failover must preserve authorization correctness

Baseline critical paths #

Main C1 paths:

  • P1 start authorization flow
  • P2 authenticate and verify policy
  • P3 grant or deny consent
  • P4 issue authorization code
  • P5 exchange code for tokens
  • P6 refresh token
  • P7 revoke token or grant
  • P8 update client config / policy
  • P9 rotate signing keys
  • P10 route to shard owner
  • P11 reassign shard ownership

Main R1 path:

  • P12 introspect / validate / userinfo / JWKS reads

This design is driven by:

  • authoritative current grant state
  • safe issuance of one-time codes and signed tokens
  • current client trust and consent state
  • revocation and key rotation correctness

Step 3 - Primary State Extraction #

For an OAuth2/OIDC authorization server, the minimal primary state is the authorization request, flow lifecycle, consent grant state, token grant state, issued credentials, client config, policy, signing key lifecycle, and routing/ownership state.

Candidate object labelCandidate sourceCandidate needed for C1/R1?Candidate decomposition actionClassPrimary?OwnerEvolutionScope kindScope value
AuthorizationRequestdirect nounYeskeep as candidateeventYesserviceappend-onlyinstanceauth_request_id
AuthorizationFlowStatelifecycle objectYeskeep as candidateprocessYesservicestate machineinstanceflow_id
ConsentGrantStatelifecycle objectYeskeep as candidateprocessYesservicestate machineinstanceuser_id + client_id + scope_set
AuthorizationCodedirect nounYeskeep as candidateeventYesserviceappend-onlyinstancecode_id
TokenGrantStatelifecycle objectYeskeep as candidateprocessYesservicestate machineinstancegrant_id
IssuedTokendirect nounYeskeep as candidateeventYesserviceappend-onlyinstancetoken_id or jti
ClientConfigdirect nounYeskeep as candidateentityYesserviceoverwriteinstanceclient_id
AuthorizationPolicydirect nounYeskeep as candidateentityYesserviceoverwriteinstancetenant_id or policy_scope
SigningKeyStatelifecycle objectYeskeep as candidateprocessYesservicestate machineinstancekey_id
PartitionOwnershiphidden write targetYeskeep as candidateprocessYesservicestate machineinstanceshard_id
PartitionMaphidden write targetYeskeep as candidateentityYesserviceoverwritecollectiontenant/shard map
AuthorizationActivityViewderived read modelNoreject as UI artifactprojectionNoderivedoverwritecollectionuser_id or tenant

Important modeling choices #

AuthorizationFlowState #

Primary because:

  • flows often span multiple steps:
    • request received
    • user authenticated
    • consent pending
    • consent granted/denied
    • code issued

ConsentGrantState #

Primary because:

  • consent may be reusable and revocable
  • current user/client/scope authorization matters for future flows

TokenGrantState #

Primary because:

  • current grant lifecycle governs:
    • code redeemed or not
    • refresh-token active or rotated
    • revoked or expired

IssuedToken #

Primary because:

  • each issued token/ID token is an immutable fact
  • useful for audit, jti tracking, replay protection, and revocation metadata

SigningKeyState #

Primary because:

  • key lifecycle matters:
    • active
    • next
    • retired
    • revoked

Minimal strict primary set #

The strongest minimal set is:

  • AuthorizationRequest
  • AuthorizationFlowState
  • ConsentGrantState
  • AuthorizationCode
  • TokenGrantState
  • IssuedToken
  • ClientConfig
  • AuthorizationPolicy
  • SigningKeyState
  • PartitionOwnership
  • PartitionMap

Step 4 - Hard Invariants #

For an OAuth2/OIDC authorization server, the hard invariants are about valid client trust/config, guarded flow and grant transitions, one-time code redemption, correct token issuance, and safe revocation/key rotation.

PathTierTypeInvariant statement
P1 start authorization flowHARDuniquenessKey auth_request_id maps to at most one logical outcome recorded authorization request within flow scope.
P2 authenticate and verify policyHARDeligibilityAction advance_authorization_flow is valid only if current AuthorizationFlowState, ClientConfig, and AuthorizationPolicy allow the transition at decision time.
P3 grant or deny consentHARDeligibilityAction advance_consent_grant is valid only if current flow state, user identity, and requested scopes allow consent transition at decision time.
P4 issue authorization codeHARDeligibilityAction issue_authorization_code is valid only if current flow state is issuable, redirect/client binding is valid, and current policy allows issuance at decision time.
P5 exchange code for tokensHARDeligibilityAction redeem_code_for_tokens is valid only if the code exists, is unredeemed, unexpired, correctly bound to client/redirect/PKCE context, and current SigningKeyState is active at decision time.
P5 exchange code for tokensHARDuniquenessKey authorization_code_id maps to at most one logical outcome successful redemption within code scope.
P6 refresh tokenHARDeligibilityAction refresh_grant is valid only if current TokenGrantState is active, refresh token is valid/unrevoked, and rotation policy allows the transition at decision time.
P7 revoke token or grantHARDeligibilityAction revoke_grant is valid only if current TokenGrantState allows revocation at decision time.
P8 update client config / policyHARDorderingClient-config and policy revisions are ordered by monotonic version within their scopes.
P9 rotate signing keysHARDeligibilityAction advance_signing_key_state is valid only if current SigningKeyState lifecycle and trust-publication rules allow the transition at decision time.
P10 route to shard ownerHARDuniquenessKey shard_id maps to at most one logical outcome current authoritative owner within shard_id.
P11 reassign shard ownershipHARDeligibilityAction reassign_shard is valid only if current owner is failed or relinquished and candidate owner is eligible and sufficiently current on shard_id at decision time.
P12 introspect / validateHARDfreshnessValidation, introspection, and userinfo reflect authoritative grant/session/key state within configured consistency bound.

What matters most #

1. Authorization code redemption is one-time #

This is one of the central protocol invariants.

2. Token issuance is guarded by current flow, client, and key state #

Wrong redirect/client binding or wrong key state breaks security.

3. Refresh-token lifecycle is current state #

Rotation, revocation, reuse detection, and expiry all depend on the authoritative current grant state.

4. Revocation must affect future introspection #

If a grant is revoked, future refresh and introspection must reflect it.


Step 5 - Execution Context #

For the baseline OAuth2/OIDC authorization server:

FieldValueWhy
Topologysingle service distributedone logical authorization server spread across auth, grant, and config nodes
Write coordination scopeper object scopecorrectness is per auth flow, consent grant, token grant, client config, key lifecycle, and shard ownership scope
Read consistency targetstrong onlyauthorization, token issuance, revocation, and introspection are security-critical
Holder modelclientuser/browser and client app drive grant/session lifecycle through current server-side state
Compensation acceptable?Nowrong token issuance or stale revocation cannot be safely repaired afterward

Derived implications #

  • holder_may_crash = true

    • browser/client flows can disappear mid-flow, and nodes can fail mid-grant transition
  • cross_service_write = false

    • baseline keeps auth, consent, grant, trust config, and key state in one logical service
  • bounded_staleness_allowed = false

    • security-critical reads should use authoritative state
  • cross_service_atomicity_required = false

    • no multi-service transaction across unrelated services in baseline
  • exclusive_claim_required = true

    • shard ownership must be exclusive
  • guarded_by_current_state = true

    • auth, consent, code redemption, refresh, revocation, and key rotation all depend on current state

What this implies #

This pushes us toward:

  • one authoritative owner per tenant/grant shard
  • append-oriented auth-request, code, and token issuance records
  • current-value client config and policy
  • guarded flow/grant/key lifecycle transitions

Step 6 - Deterministic Mechanism Selection #

PathWrite shapeBase mechanismRequired companions
P1 start authorization flowappend-only eventappend logcorrelation id, state token
P2 authenticate / verify policyguarded state transitionCAS on (state, version) or single writer per shardflow version, PKCE/state binding
P3 grant or deny consentguarded state transitionCAS on (state, version)consent version
P4 issue authorization codeappend-only event guarded by current stateone-time code issuanceredirect binding, PKCE metadata
P5 exchange code for tokensguarded state transition plus append issuanceCAS on grant/code state, then signed token issuanceone-time redemption marker, active key
P6 refresh tokenguarded state transition plus append issuanceCAS on grant staterotation/reuse detection, active key
P7 revoke token / grantguarded state transitionCAS on (state, version)grant version
P8 update client config / policyoverwrite current valueCAS on versionconfig/policy version
P9 rotate signing keysguarded state transitionlifecycle state transitionkey version, JWKS publication epoch
P10 route to shard ownerexclusive claimleasefencing token, heartbeat
P11 reassign shard ownershipguarded state transitionCAS on (state, version)fencing token, shard catch-up check

Why these fit #

These depend on current state, factors, and policy, so guarded transitions fit.

Code and token issuance #

Issuance creates immutable credentials, but only under current valid flow/grant/client/key state.

Client config and policy #

These are current-value control state, so overwrite fits.

Key rotation #

This is lifecycle-managed current state, so guarded transition fits.

Canonical substrate implied #

The baseline now points to:

  • sharded authorization-server service
  • one owner per tenant/grant shard
  • current flow, consent, and grant lifecycle state
  • append-only code/token issuance records
  • current client config, policy, and signing-key lifecycle

Step 7 - Read Model / Source of Truth #

For an OAuth2/OIDC authorization server, truth is mostly direct source state. Activity and audit views are derived.

ConceptTruthRead pathRebuild path
C1 authorization flow initiationAuthorizationRequestread source directlyauthoritative auth-request store
C2 current authorization flow lifecycleAuthorizationFlowStateread source directlyauthoritative flow-state store
C3 consent grant lifecycleConsentGrantStateread source directlyauthoritative consent store
C4 authorization code issuanceAuthorizationCoderead source directlyauthoritative code store
C5 current token grant lifecycleTokenGrantStateread source directlyauthoritative grant store
C6 issued token historyIssuedTokenread source directlyauthoritative issuance/audit store
C7 current client trust configClientConfigread source directlyauthoritative client-config store
C8 current auth policyAuthorizationPolicyread source directlyauthoritative policy store
C9 signing key lifecycleSigningKeyStateread source directlyauthoritative key store
C10 shard ownershipPartitionOwnershipread source directlyauthoritative ownership store
C11 shard routing mapPartitionMapread source directlyauthoritative routing metadata
C12 activity / audit viewsderived from requests, grants, and issuancematerialized viewrecompute from authoritative state

Important point #

For the core semantics:

  • authorization and issuance read authoritative client config, policy, and key state
  • introspection reads authoritative grant/session state
  • audit/activity views are projections

Step 8 - Failure Handling #

PathRetryCompeting writersCrash after commitPublish failureStale holder
P1 start authorization flowretry safe with state/correlation tokenparallel flows coexist per user/clientcommitted request survives crash if persistedbrowser redirect may retrystale shard owner blocked by fencing token
P2 authenticate / verify policyretry safe with flow version/challenge tokenstale or duplicate response loses guarded transitioncommitted flow transition survives crash if persistedexternal MFA/send step may retrystale shard owner blocked by fencing token
P3 grant or deny consentretry with consent/flow versionstale consent write loses guarded transitioncommitted consent survives crash if persistedbrowser post may retrystale shard owner blocked by fencing token
P4 issue authorization coderetry may create multiple codes unless flow is fenced and one-time issuance is enforcedissuance must be fenced by current flow statecommitted code survives crash if persistedbrowser redirect may retrystale issuer blocked by ownership/version discipline
P5 exchange code for tokensretry with same code may race; only first successful redemption should winstale or duplicate redemption loses one-time code transitioncommitted token issuance survives crash if audit persistedclient may not receive response even after issuancestale issuer blocked by ownership/version discipline
P6 refresh tokenretry can create duplicate refresh outcomes unless rotation policy and grant version are fencedstale refresh loses guarded transitioncommitted refresh survives crash if persistedclient may retry after lost responsestale issuer blocked by ownership/version discipline
P7 revoke token / grantretry with grant versionstale revoke loses guarded transitioncommitted revocation survives crash if persistedintrospection propagation must reflect authoritative staten/a
P8 update client config / policyretry with config versionstale update loses CAScommitted config survives crash if persistedconfig propagation may lagn/a
P9 rotate signing keysretry with key version/lifecycle epochstale rotation loses guarded transitioncommitted key transition survives crash if persistedJWKS propagation may lagn/a
P10 route to shard ownerretry after refreshing shard maponly one valid owner should existif owner changed, refreshed map points to new ownern/astale owner rejected by fencing token
P11 reassign shard ownershipretry failover transition safelyonly one reassignment wins current ownership statepromoted owner crash triggers later reassignmentn/aold owner fenced and must not continue serving
P12 introspect / validate / userinforead retry safemany readers coexistnode crash drops request onlyrelying party may retrystale validation forbidden beyond consistency bound

What matters most #

1. Authorization code redemption must be one-time #

Otherwise replay breaks the protocol.

2. Refresh-token rotation must be fenced #

Lost responses and replayed refreshes are common failure cases.

3. Issuance and delivery are separate #

The server may issue tokens successfully even if the client never receives the response.

4. Key rotation needs overlap #

New JWKS material must be available before old keys retire.


Step 9 - Scale Adjustments #

HotspotTypeFirst response
login / authorization spikeswrite throughput hotspotshard by tenant/user/grant scope and add more auth nodes
hot grant/session tenantscontention hotspotisolate large tenants and partition grant state more finely
client-config and JWKS reads on hot pathread hotspotcache config and key material with strict versioning and short refresh bounds
introspection volumeread hotspotuse fast grant/session shards or short-lived self-contained access tokens where appropriate
audit/activity queriesread hotspotserve from projections, not hot auth path
refresh stormscontention hotspotjitter refresh windows and enforce rotation/reuse detection efficiently

What scales well #

This system scales by:

  • sharding grant and tenant state
  • separating hot authorization/token reads from audit projections
  • caching client config and JWKS carefully under strict version control
  • using short-lived access tokens to reduce introspection and revocation pressure

What fails first #

Usually:

  • login or consent spikes
  • very large tenants with hot grant stores
  • mismanaged refresh-token rotation
  • introspection load from many relying parties

Canonical design conclusion #

The mechanical outcome is:

  • primary state:
    • AuthorizationRequest
    • AuthorizationFlowState
    • ConsentGrantState
    • AuthorizationCode
    • TokenGrantState
    • IssuedToken
    • ClientConfig
    • AuthorizationPolicy
    • SigningKeyState
    • PartitionOwnership
    • PartitionMap
  • critical invariants:
    • guarded authorization-flow, consent, and grant lifecycle
    • one-time code redemption
    • issuance valid only under current grant, client config, policy, and active key state
    • refresh/revocation reflected in future introspection
    • exclusive shard ownership for grant truth
  • mechanisms:
    • append log
    • guarded flow/grant/key transitions
    • current-value client config and policy
    • signed credential issuance
    • fenced shard ownership
  • reads:
    • direct authoritative reads for authorization, introspection, and trust decisions
    • projections for activity and audit UX

Polished interview answer #

I’d build the OAuth2/OIDC authorization server as a sharded grant-management service with one authoritative owner per tenant or grant shard. Authorization requests are recorded as flow facts, authentication and consent advance guarded flow and consent state machines, and successful flows issue one-time authorization codes bound to client, redirect, and PKCE context. Code exchange and refresh operate on authoritative grant state, so redemption, rotation, reuse detection, and revocation are all guarded transitions. Access tokens, refresh tokens, and ID tokens are issued as signed immutable credentials, but only if current client config, policy, consent, grant, and signing-key state all allow issuance. The main scaling levers are more grant shards, careful caching of client config and JWKS, short-lived access tokens, and keeping audit/activity views off the hot token path.


Concrete Substrate #

I’ll choose a sharded authorization-server service with durable flow/grant state plus separate signing-key and client-config stores as the concrete baseline, because it matches the mechanics we derived:

  • append-only auth requests, codes, and token issuance records
  • guarded flow, consent, and grant lifecycle
  • current-value client config and policy
  • managed signing-key lifecycle
  • one owner per shard

Concrete tech family:

  • authorization service in Go, Java, or Rust
  • durable metadata/state storage:
    • replicated DB or RocksDB-backed service state
  • shard replication:
    • Raft or leader-follower replication with commit index
  • signing:
    • HSM/KMS-backed keys or managed signing service
  • metadata/control:
    • etcd or internal metadata quorum for shard ownership/routing

Each shard owner stores:

  • authorization-request log for owned scope
  • current flow state
  • current consent state
  • current grant state
  • code and issuance history / audit references
  • current client config and policy cache
  • key-reference metadata for active signing material

Operation Layer #

1. Start authorization flow #

API

  • StartAuthorization(client_id, redirect_uri, scope, state, code_challenge, prompt, context)

Initiator

  • client app / browser

Entry point

  • authorization frontend

Authoritative decider

  • shard owner for tenant/client/user context

Precondition

  • client config exists and redirect URI is valid

Transition

  • append AuthorizationRequest
  • create AuthorizationFlowState = STARTED

Response

  • login/consent redirect or next auth step

API

  • AdvanceAuthorizationFlow(flow_id, factor_response, consent_response, expected_version?)

Initiator

  • user/browser

Entry point

  • authorization frontend

Authoritative decider

  • shard owner for auth flow

Precondition

  • current flow state accepts this transition
  • client config and policy allow requested scopes

Transition

  • guarded update of AuthorizationFlowState
  • guarded update of ConsentGrantState if consent is granted or denied

3. Issue authorization code #

API

  • IssueAuthorizationCode(flow_id, expected_version?)

Initiator

  • system after successful auth + consent

Entry point

  • authorization frontend

Authoritative decider

  • shard owner for flow/grant

Precondition

  • current flow state is issuable
  • redirect/client/PKCE context valid

Transition

  • append AuthorizationCode
  • advance flow state to issued

4. Redeem code for tokens #

API

  • RedeemAuthorizationCode(client_id, code, code_verifier, redirect_uri)

Initiator

  • client app/backend

Entry point

  • token endpoint

Authoritative decider

  • shard owner for code/grant plus active key state

Precondition

  • code exists, unredeemed, unexpired, and bound to this client/redirect/PKCE context

Transition

  • guarded update of TokenGrantState
  • append IssuedToken records
  • sign and return access token / refresh token / ID token as appropriate

5. Refresh token #

API

  • RefreshToken(client_id, refresh_token)

Initiator

  • client app/backend

Entry point

  • token endpoint

Authoritative decider

  • shard owner for grant plus active key state

Precondition

  • current grant state is refreshable
  • refresh token valid and unrevoked

Transition

  • guarded update of TokenGrantState
  • append new IssuedToken records
  • optionally rotate refresh token and invalidate old one

6. Revoke token / grant #

API

  • RevokeGrant(token_or_grant_id, actor, expected_version?)

Initiator

  • client, user, or admin

Entry point

  • revocation endpoint / admin API

Authoritative decider

  • shard owner for grant

Precondition

  • current grant state is revocable

Transition

  • guarded update TokenGrantState -> REVOKED

7. Rotate signing key #

API

  • internal admin/key-management flow

Initiator

  • system/admin

Entry point

  • key-management API

Authoritative decider

  • key-state owner

Precondition

  • new key material available
  • JWKS/publication rules satisfied

Transition

  • SigningKeyState: NEXT -> ACTIVE
  • prior active key moves to retiring/retired later

Entry Point vs Decider vs Responder #

PathEntry pointAuthoritative deciderPhysical responderLogical responder
start authorizationauthorization frontendflow/grant shard ownerfrontend nodeauthorization server
advance auth/consentauthorization frontendflow shard ownerfrontend nodeauthorization server
redeem code / refreshtoken endpointgrant shard owner + active key statetoken nodeauthorization server
revoke grantrevocation endpointgrant shard ownerAPI nodeauthorization server
rotate keykey-management APIkey-state ownercontrol-plane nodeauthorization server
shard failoverfollower / coordination layershard quorum / lease storenew leader / control planeauthorization server

Concrete HLD #

Main components:

  • authorization frontend
    • handles browser redirects, consent pages, and callbacks
  • token endpoint / grant shard owners
    • authoritative owners for code/grant lifecycle and token issuance
  • client-config / policy service
    • stores tenant policy and client metadata
  • signing/key service
    • manages active/next/retired signing material and JWKS
  • metadata/control service
    • tracks shard ownership and routing
  • audit/activity pipeline
    • serves activity views and compliance reporting

Short Interview Version #

I’d build the OAuth2/OIDC authorization server as a sharded grant-management service with one authoritative owner per tenant or grant shard. Authorization requests are recorded as flow facts, authentication and consent advance guarded flow and consent state machines, and successful flows issue one-time authorization codes bound to client, redirect, and PKCE context. Code exchange and refresh operate on authoritative grant state, so redemption, rotation, reuse detection, and revocation are all guarded transitions. Access tokens, refresh tokens, and ID tokens are issued as signed immutable credentials, but only if current client config, policy, consent, grant, and signing-key state all allow issuance. The main scaling levers are more grant shards, careful caching of client config and JWKS, short-lived access tokens, and keeping audit/activity views off the hot token path.