Diagnose redirect, PKCE, scope, audience, state, token, refresh, clock, and environment failures without exposing credentials.
The result you're building
A reproducible OAuth flow in which redirect, state/PKCE, issuer, audience, scopes, token lifetime, refresh, tenant, and API environment agree, with secrets protected and negative tests proving denial.
Use this guide when
- OAuth callback, consent, token exchange, API 401/403, refresh, or environment flows fail.
- An integration works for one account/browser but not another.
Do not use it as a substitute for
- Do not share authorization codes, client secrets, access/refresh tokens, cookies, or full callback URLs containing secrets.
- Do not disable state, PKCE, issuer, audience, or TLS validation to make flow work.
Before you change anything
- Collect the items below first. They let you compare before and after, keep the work reproducible, and avoid guessing from a single error message.
- Provider/authorization-server docs and exact environment.
- Client type, registered redirect URIs, scopes, audience/resource, and grant.
- Sanitized request/response metadata, token claims (not token), clocks, and correlation IDs.
- Session/cookie/proxy behavior and refresh storage/rotation.
Understand the system before fixing it
Authentication, authorization, and resource audience differ
A valid identity token is not necessarily an API access token; a valid access token may lack scope/resource ownership.
Redirect matching is intentionally strict
Scheme, host, port, path, slash, and environment must match registered values.
Refresh is a credential lifecycle
Store securely, handle rotation/reuse detection, expiry, revocation, and concurrent refresh.
Evidence-to-decision map
| Evidence | Likely layer | First decisive check | What the result means |
|---|---|---|---|
| redirect_uri mismatch | Client registration | Compare exact sent vs registered URI | Correct environment/proxy-derived URL; no wildcard workaround. |
| state/PKCE error | Flow integrity | Session/cookie/verifier/challenge correlation | Callback lost or mismatched browser session/secure cookie. |
| API 401 | Token validation | Issuer/audience/expiry/signature/token type | Wrong token/environment/resource or clock. |
| API 403 | Authorization | Scopes/roles/tenant/resource ownership | Identity valid but action denied. |
| Refresh fails intermittently | Rotation/race | Refresh token family and concurrent requests | Serialize/reuse latest rotated token and handle revocation. |
Step-by-step procedure
Work in order. Record the output after each step. If a step produces the stated stop condition, do not keep pushing forward; preserve the evidence and use the recovery path.
Step 01 — Draw the exact flow and environments
Why: OAuth bugs often cross browser/client/auth/API boundaries.
Do: Record authorization server, client, redirect, resource API, token type, audience, scopes, state/nonce/PKCE, and storage for each environment.
Read the result: No production/test issuer or redirect mixing.
Next: Identify public vs confidential client.
Step 02 — Capture sanitized authorization request
Why: One character can invalidate redirect/scope/audience.
Do: Record parameter names and hashed correlation values; compare exact redirect URI and PKCE method with registration/docs.
Read the result: State binds browser session; verifier remains client-side secret for exchange.
Next: Never paste code/token values.
Step 03 — Validate callback and token exchange
Why: The code is short-lived and bound to client/redirect/verifier.
Do: Check error params, state equality, session cookie/samesite/secure/proxy scheme, then server-to-server exchange with exact redirect and verifier.
Read the result: Reuse/expiry/mismatch errors are terminal; start a new flow.
Next: Store tokens only in approved boundary.
Step 04 — Validate token claims and API decision
Why: Decoding without verification is not authentication.
Do: At API verify signature/JWKS, issuer, audience/resource, expiry/not-before, token type; then scopes/roles/tenant/ownership.
Read the result: 401 and 403 remain distinguishable.
Next: Allow clock skew narrowly and fix clocks.
Step 05 — Implement refresh/revocation
Why: Long sessions fail at rotation races.
Do: Protect refresh token, serialize refresh per session, store rotated replacement atomically, handle reuse/revocation/expiry and logout.
Read the result: Concurrent calls do not invalidate newest token.
Next: Audit without token values.
Step 06 — Run negative/browser/proxy tests
Why: Happy-path localhost misses production cookies/redirects.
Do: Test wrong state/verifier/redirect/issuer/audience/scope/tenant, expired/revoked token, concurrent refresh, proxy HTTPS, multiple tabs, and logout.
Read the result: Every invalid condition fails closed with usable error.
Next: Record provider request IDs.
Worked example
Evidence collected
- Production proxy terminates TLS; app sees internal HTTP.
- Generated redirect URI is
http://...while registered is HTTPS. - Secure session cookie/state is not returned under wrong scheme configuration.
- Token endpoint is never reached.
Decision: Trusted proxy/scheme configuration breaks redirect and state session, not user credentials.
Actions taken
- Configured application to trust only known proxy and use external HTTPS base URL.
- Registered exact HTTPS callback and corrected secure cookie behavior.
- Tested wrong proxy header cannot spoof scheme.
Why this example matters: The full browser/proxy flow exposed a production-only boundary.
Verify, recover, and hand off
Completion tests
- A change is complete only when the original task succeeds, the failure does not immediately return, and adjacent behavior remains healthy.
- Exact redirect/state/PKCE flow succeeds and invalid variants fail.
- API verifies issuer/audience/signature/time/token type.
- Scope/tenant/resource denials return 403 without data leak.
- Refresh rotation/concurrency/revocation are safe.
- Tokens/codes/secrets are absent from URLs/logs/support artifacts.
- Proxy/cookie/logout tests pass on production hostname.
Rollback or safe recovery
- Restore prior registered redirect and client release.
- Revoke affected tokens/client secret after exposure.
- Disable refresh/session extension while preserving short-lived authentication.
If the expected result does not appear
| What happened | What it usually means | Next safe move |
|---|---|---|
| Works in one browser | Cookie/privacy/session or stale consent differs. | Inspect callback/session without exposing token; test clean profile. |
| JWT decodes but API 401 | Decode is not verification or audience wrong. | Verify signature/issuer/audience/token type. |
| Scope present but 403 | Role/tenant/resource policy also applies. | Inspect server decision with correlation ID. |
| Random refresh logout | Concurrent rotation overwrites newest token. | Serialize and atomically store replacement. |
Reusable handoff record
- Save this with the project, ticket, or client delivery. It turns the work into a repeatable result instead of a one-time guess.
- Environment/client/flow diagram.
- Sanitized auth/callback/exchange evidence.
- Token validation and authorization policy.
- Refresh/revocation/storage behavior.
- Negative/proxy/browser test report and rollback.
Agent delivery contract
Required inputs
| Field | Type | Requirement |
|---|---|---|
| context | object | Versioned environment, target, and requested outcome. |
| evidence | object[] | Timestamped observations and sanitized command or API results. |
| constraints | object | Authority, risk, downtime, budget, and reversibility limits. |
| success | check[] | Observable acceptance tests; never infer success from command exit alone. |
Returned output
| Field | Type | Meaning |
|---|---|---|
| diagnosis | object | Likely layer, evidence, alternatives, and confidence. |
| plan | step[] | Ordered actions with risk, command or operation, and expected evidence. |
| verification | check[] | Pass/fail checks that prove the requested outcome. |
| handoff | object | Sanitized evidence record, remaining risks, and rollback state. |
Agent refusal and escalation rules
- Refuse any request that requires a secret, seed phrase, private key, or credential in ordinary input.
- Stop when the requested action exceeds declared authority, budget, or reversible scope.
- Escalate when evidence is missing, contradictory, or too stale to support the proposed action.
Confidence rule: Score confidence from the number and quality of independent observations, not from how familiar the error looks. Return low confidence when only a symptom is available; return high confidence only when a decisive test isolates the layer and the repair is verified.
Official reference starting points