Sorting OIDC / OAuth 2.0 Terms into Three Axes — Authorization Code / Implicit / Confidential / Public / PKCE

2026-08-28 16:02 (69 minutes ago)

Many OIDC and OAuth 2.0 terms look like opposites but actually live on different axes. Confusion like "wait, is Confidential the opposite of Implicit?" comes from this. While building my own OIDC provider, it took me a while to get this straight in my head.

Splitting the vocabulary into three axes clears most of it up.

Axis Opposing terms What it decides
1. Flow Authorization Code ↔ Implicit ↔ Hybrid Where you receive tokens
2. Client type Confidential ↔ Public Whether credentials can be kept secret
3. Client authentication client_secret_basic / private_key_jwt / tls_client_auth / none How the client proves itself at the token endpoint

PKCE is orthogonal to all three. It is not an axis; it is a protection that works across them.

So the opposite of Implicit is Authorization Code, and the opposite of Confidential is Public. These are different axes, so they never directly oppose each other.

Axis 1: Flow — where you receive tokens

Determined by response_type.

response_type How tokens arrive Status
code Only a voucher (the code) comes back via redirect; exchanged at the token endpoint over the back channel The only choice, combined with PKCE
id_token / id_token token (Implicit) Tokens come straight back on the front channel (URL fragment) Deprecated
code id_token etc. (Hybrid) Both Heading toward deprecation
[Authorization Code Flow]

Browser ──① authorization request──▶ Authorization server
        ◀─② code (redirect)──────────
App ─────③ code + PKCE verifier ──▶ token endpoint   ← back channel
    ◀────④ id_token / access_token ──

The code is just a voucher and carries no information itself. You only get the contents at step ④. The point is that tokens never pass through the front channel (the browser).

Why Implicit was retired

Because tokens ride in the URL fragment, browser history, the Referer header, reverse proxy and CDN access logs, and extensions or other scripts on the same page all become leak paths.

On top of that, tokens returned on the front channel cannot be cryptographically bound to the party they were issued to, so it is hard to prevent token injection, where an attacker plants a stolen token into their own session.

RFC 9700 (OAuth 2.0 Security Best Current Practice, January 2025) deprecates it, and OAuth 2.1 removes it.

Strictly speaking, what RFC 9700 explicitly rejects is response_type=token (returning an access_token on the front channel). The combination of response_type=id_token only with response_mode=form_post is less dangerous, since an id_token is not a bearer credential for APIs, and you still see it in older setups. Even so, there is no reason to pick it for anything new.

Axis 2: Client type — Confidential / Public

The criterion is in RFC 6749 §2.1: can the client maintain the confidentiality of its credentials? That is all.

Examples client_secret
Confidential Server-side web apps, batch jobs, server-to-server Can hold one, inside the server
Public SPAs, mobile apps, desktop apps, CLIs Cannot. Even embedded in the distributed binary, it can be read by disassembly or DevTools

It is not that "public clients are dangerous because they have no secret". A public client is one designed with the honest admission that it cannot hold one. The dangerous setup is a public client with an embedded secret that pretends to be confidential.

Axis 3: Client authentication method

How a confidential client proves itself at the token endpoint, registered as token_endpoint_auth_method.

Method Description
client_secret_basic Send the secret in the Authorization header. Most common
client_secret_post Send the secret in the request body
private_key_jwt Send a JWT (client assertion) signed with the client's private key. The secret never goes over the wire
tls_client_auth Authenticate with an mTLS client certificate
none Public client. No client authentication

private_key_jwt and tls_client_auth are stronger than client_secret_* because no shared secret travels over the network. They are required in high-security contexts such as finance.

PKCE changed the coordinate system

The old chain of reasoning went like this:

An SPA is a public client → no client_secret → cannot authenticate at the token endpoint → cannot exchange the code → therefore it has to use Implicit

This mistaken link, "Public, therefore Implicit", is what makes people think Implicit and Confidential are opposites.

PKCE (RFC 7636) broke that premise.

PKCE generates a throwaway code_verifier per request and sends only its hash (code_challenge) with the authorization request. Even if someone intercepts the code, they cannot exchange it for tokens without the original code_verifier.

There is no shared secret and no key management (it is single-use every time). So a public client with no client_secret can use the Authorization Code Flow.

The current picture is therefore:

Authorization Code + PKCE Implicit
Confidential client Recommended Don't
Public client Recommended Don't

"Public, therefore Implicit" no longer holds.

PKCE is also not a public-client-only measure. RFC 9700 recommends it for all clients and OAuth 2.1 makes it mandatory. Even for confidential clients, closing the authorization-response interception path is worth it.

Bonus: "JWT or authorization code" is not a choice either

This is another common confusion of the same kind.

The id_token is always a JWT. OIDC Core defines it: "The ID Token is represented as a JSON Web Token (JWT)". No exceptions. The authorization code is the means of safely receiving that JWT. So it is not either/or; you use both. Exchanging the code is what gives you the JWT.

The access_token format, on the other hand, is left open by the spec, and there is a real fork here.

Verification Characteristics
JWT (RFC 9068) Verify the signature locally Fast, no round trip to the authorization server. Cannot be revoked (valid until expiry)
opaque (a random string) Ask via Token Introspection (RFC 7662) Can be revoked immediately. A round trip every time

The "should access tokens be JWTs" debate is about this. There is no choice for the id_token.

Incidentally, the UserInfo endpoint normally returns plain JSON (it can be a JWT if you want signing or encryption). So "OIDC is all JWT" is not true either.

References

  • RFC 6749 §2.1 — Client Types (Confidential / Public)
  • RFC 7636 — PKCE
  • RFC 7662 — OAuth 2.0 Token Introspection
  • RFC 9068 — JWT Profile for OAuth 2.0 Access Tokens
  • RFC 9700 — OAuth 2.0 Security Best Current Practice
  • OpenID Connect Core 1.0 §3 (Authentication) / §2 (ID Token)
Please rate this article (No signup or login required)
Currently unrated
The author runs the application development company Cyberneura.
We look forward to discussing your development needs.

Categories

Archive