OAuth for public clients: the modern flow that should pass the tests
RFC 9700 makes today's baseline explicit: authorization code, PKCE for public clients, exact redirect URI matching, mix-up protection, and retirement of implicit and password grants.
- Nature
- Engineering baseline based on published RFCs
- Audience
- Architecture, development, application security, IAM, and QA
- Published and verified
- 4 August 2026
- Limit
- The final profile depends on client type, authorisation server, resources, and threat model

In brief
Native applications and browser frontends cannot keep a client secret secret; security must come from the flow. The client creates a unique verifier and sends only the challenge, binds the response to the transaction, validates the correct issuer, and exchanges the code through the intended channel. PAR reduces request exposure in the browser; DPoP can bind tokens to a key holder; audience and scope limit where and why a token works. The matrix below turns standards into testable acceptance criteria.
- Public clients must use PKCE; S256 is the transformation to require where supported.
- The implicit grant exposes tokens at the authorisation endpoint and is no longer the recommended choice; use response type code.
- The resource owner password credentials grant must not be used.
- PKCE does not replace every check: redirect URI, issuer, state or nonce, audience, and token policy still matter.
The secure design removes fake secrets and binds every response to the right transaction
A secret packaged in a native app or delivered to a browser can be extracted; do not use it as proof of a public client's identity. PKCE creates per-transaction proof: the client keeps the verifier, sends the challenge, and only a party presenting the matching verifier can exchange the code.
When a client talks to more than one authorisation server, validate the response issuer or use an equivalent countermeasure. Exact redirect URI matching reduces code leakage; state or equivalent protection binds the response to the session; nonce protects specific OpenID Connect properties.
Authorisation flow with explicit verification
- 1Create transaction
Generate unique verifier, S256 challenge, state, and nonce where applicable.
- 2Send request
Use an external browser and preregistered redirect URI; use PAR where the profile requires it.
- 3Validate return
Compare state, issuer, redirect, and context before accepting the code.
- 4Exchange code
Present the verifier to the correct token endpoint and reject reuse or mismatch.
- 5Use limited token
Validate audience, scope, expiry, and DPoP proof when adopted.
Every control needs a negative test, not just configuration
Test what the system rejects. A happy path confirms interoperability; it does not prove that a stolen code, swapped issuer, or nearly matching redirect fails securely.
Threat, control, and acceptance criterion
| Threat | Control | Negative test | Acceptance |
|---|---|---|---|
| Code interception | PKCE S256 | Exchange without verifier or with another | Fails without token |
| Server mix-up | iss validation | Return a different issuer | Client rejects before exchange |
| Redirect leakage | Exact matching | Change path, host, or query | Request or return rejected |
| Request tampering | PAR | Change browser parameters | Server uses pushed request |
| Token replay | DPoP where applicable | Use token without key or from another origin | Resource rejects proof |
| Excess privilege | Audience and scope | Use token at another resource or action | Resource refuses |
The release gate should prove client, server, and resource as one system
Automate tests against the real authorisation-server configuration and a representative resource server. Validate logs too: they must not retain full codes, verifiers, tokens, or sensitive parameters. Rotation and revocation should work without reinstalling the entire app.
PAR and DPoP solve specific threat classes; do not treat them as universal seals. DPoP loses value if both token and key are stolen from the same compromised context. Start from the actual attacker and the mechanism's operational cost.
- Reject implicit and password grants for new clients.
- Use an external browser for native apps, not an embedded webview.
- Maintain an exact redirect allowlist per environment.
- Test expiry, refresh rotation, revocation, logout, and clock skew.
Sources consulted
Consulted on the stated update date.
Opens in a new window.- 01RFC 9700 — Best Current Practice for OAuth 2.0 SecurityRFC Editor · SYS-RFC
- 02RFC 7636 — Proof Key for Code ExchangeRFC Editor · SYS-RFC
- 03RFC 9126 — Pushed Authorization RequestsRFC Editor · SYS-RFC
- 04RFC 9207 — Authorization Server Issuer IdentificationRFC Editor · SYS-RFC
- 05RFC 9449 — Demonstrating Proof of PossessionRFC Editor · SYS-RFC
- 06RFC 8252 — OAuth 2.0 for Native AppsRFC Editor · SYS-RFC
Participate
Share the analysis. Add context.
Distribute the article through your channel or leave an observation connected to the topic.

Comments
Loading the conversation.