Confidential vs Public Clients in OAuth 2.0
Not every app can keep a secret — and OAuth 2.0 treats the ones that can't very differently. Here's how the protocol tells them apart, and why that decision quietly shapes an app's entire security model.
Imagine two employees at a company. The first works inside a locked, badge-only server room that no outsider can physically enter — if you hand that employee a written password, it stays inside a room nobody unauthorized can walk into. The second employee works at a public information desk in the middle of a shopping mall, greeting anyone who walks up. If you handed that second employee a written password and asked them to keep it secret, it would only be a matter of time before someone peeked over their shoulder, photographed it, or simply asked to see it. OAuth 2.0 makes exactly this distinction between applications. Some apps run somewhere genuinely private — a backend server you control. Others run somewhere fundamentally public — a browser tab or a device in a stranger’s pocket, where anyone determined enough can inspect the code. This tutorial explains that split, called confidential versus public clients, and why it changes almost everything about how an app is allowed to prove its identity.
1The Real Problem: Not Every App Can Keep a Secret
Before OAuth 2.0 can trust an application, it needs to answer a surprisingly basic question: can this application actually protect a secret, or not?
Early designs for delegated access assumed every application was roughly the same kind of thing — a piece of software running somewhere the developer controlled, capable of holding onto a password-like value and never exposing it. That assumption works fine for a backend web server sitting in a company’s own data center. Nobody outside the company can open that server and read its configuration files. But the moment OAuth needed to support mobile apps and browser-based apps, that assumption fell apart.
A backend server is like a safe bolted to the floor of a bank vault — the only people who can open it are the ones who already work inside the vault. A mobile app installed on someone’s phone is more like a paper note tucked into a jacket pocket — technically hidden, but if someone really wants to find it, unzipping the pocket and reading it isn’t hard at all. Anyone can decompile a mobile app or read a browser tab’s JavaScript and eventually find anything “hidden” inside it.
This isn’t a hypothetical concern. A mobile app’s installer file can be downloaded by anyone and taken apart piece by piece. A single-page web application’s entire JavaScript source is, by definition, sent to and executed inside the user’s own browser — the user’s browser has to be able to read it in order to run it. In both cases, anything embedded in that code — including a supposedly secret value — should be treated as effectively public information, because a sufficiently motivated person can always extract it.
Developers sometimes assume that obfuscating or minifying a mobile app or JavaScript bundle “hides” an embedded secret well enough. It doesn’t. Obfuscation slows down casual inspection, but it does not turn a public artifact into a private one — a determined attacker with free tools can still recover the value.
OAuth 2.0’s answer to this reality is to formally split applications into two categories — confidential clients and public clients — and to require different behavior from each. This tutorial walks through exactly what that split means, how the Authorization Server decides which category an app falls into, and how the rest of the protocol bends around that decision.
This isn’t just an academic label buried in a specification document — it directly shapes engineering decisions that teams make every single day. Whether a mobile team needs to stand up a backend service purely for authentication, whether a browser extension can safely call a third-party API directly, whether a secret needs a rotation schedule at all — every one of these questions traces back to whether the application in question is confidential or public. Getting this classification wrong early in a project tends to surface later as a security review finding, a failed compliance audit, or, worse, an actual incident where a “hidden” credential turns out to have been public all along.
2Core Concepts and Definitions
A handful of terms carry the entire chapter — once these are clear, everything else in this tutorial builds on top of them.
Confidential Client
An application that runs in an environment the developer controls and that no outside party can inspect — typically a backend server. It can reliably hold a secret value without exposing it.
Public Client
An application that runs somewhere the developer cannot guarantee privacy — a device the end user controls, like a mobile phone or a web browser. Anything embedded in it should be assumed readable by anyone.
Client ID
A public, non-secret identifier assigned to an application when it registers with an Authorization Server. It says “which app is this,” but proves nothing about trust on its own — like a name tag, not a password.
Client Secret
A private value issued only to confidential clients, used to prove “this really is the registered app, not an impostor pretending to be it,” during the token exchange step.
Client Authentication
The step where a client proves its own identity to the Authorization Server — separate from the end user proving theirs. Confidential clients authenticate; public clients typically cannot, and rely on other safeguards instead.
Think of the Client ID as a company’s public business name printed on its storefront — everyone can see it, and it identifies the business. The Client Secret is more like the combination to the store’s back-office safe — known only to trusted staff, never printed anywhere a customer could read it. A public client is a business with no back office at all — everything happens on the sales floor, in full view.
This single classification — confidential or public — is decided once, when an application is registered with an Authorization Server, and then quietly influences which grant types the app is allowed to use, whether PKCE is required, and how tokens should be stored for the rest of the app’s life.
3What This Distinction Is Not
A few common misunderstandings are worth clearing up before going further.
The terms describe an application’s ability to protect a secret, not how much a company trusts it or how important its job is. A mobile banking app is extremely trusted by its developer and handles very sensitive actions — it is still a public client, simply because the environment it runs in cannot keep secrets hidden.
Public clients are not automatically unsafe — they’re simply designed around a different assumption. Instead of proving identity with a secret, they rely on other protections, most importantly PKCE (covered in Chapter 10), plus tightly controlled redirect addresses and short token lifetimes.
It’s easy to assume “public” refers to a public-facing website in general. It actually refers specifically to where the application’s own code and configuration live. A website’s visible pages can be fully public while its backend, which never leaves the company’s servers, is still a confidential client.
In practice, an application’s architecture decides its client type for it. A single-page app cannot suddenly become confidential by policy — its JavaScript is still shipped to the browser. Changing client type usually means changing where the sensitive logic actually runs, such as adding a backend component.
4Types of Clients in the Real World
Here’s how the confidential/public split maps onto the kinds of applications people actually build.
Traditional Web Server Backend
A confidential client. The application’s code — including any client secret — runs entirely on a server the company controls. Users interact with it through their browser, but the sensitive logic and secrets never leave the server.
Single-Page Application (SPA)
A public client. Even though a backend might exist for other purposes, the SPA’s own JavaScript executes directly in the user’s browser, where any embedded secret would be readable by anyone who opens the browser’s developer tools.
Native Mobile App
A public client. The installed app package can be downloaded and reverse-engineered by anyone with the right tools, so any value baked into the app binary should be assumed exposed eventually.
Command-Line Tool or Desktop App
Also typically treated as a public client, for the same reason as mobile apps — the software runs entirely on a device the end user controls, and its files can be inspected locally.
Smart TV or Limited-Input Device App
A public client, and often paired with the Device Authorization Flow discussed in earlier OAuth tutorials, since it has no realistic way to protect a secret or accept complex user input directly.
Backend Service Calling Another Backend Service
A confidential client. Both ends of the interaction live entirely inside infrastructure the company controls, so a client secret — or a stronger credential like a private signing key — can be stored and used safely.
Browser Extension
Generally treated as a public client. Its code is distributed to and runs inside individual users’ browsers, making any embedded secret just as exposed as in a native mobile app.
A simple way to decide: if the application’s compiled code, installer, or source bundle ever leaves your own servers and lands on a device or browser you don’t control, treat it as a public client. If it never leaves infrastructure you control, it can be confidential.
5Client Authentication Methods
Confidential clients don’t just have “a secret” — OAuth defines several different ways that secret, or an alternative credential, can be presented.
| Method | Who Uses It | How It Proves Identity |
|---|---|---|
| client_secret_basic | Confidential clients | Client ID and secret sent together in a standard authentication header |
| client_secret_post | Confidential clients | Client ID and secret sent as part of the request body instead of a header |
| private_key_jwt | High-security confidential clients | Client signs a short-lived statement with its own private key, proving possession without ever transmitting a shared secret |
| mTLS (mutual TLS) | High-security confidential clients | Client presents a certificate during the secure connection itself, verified as part of establishing the network channel |
| none | Public clients | No client authentication at all — identity is asserted only by the Client ID, backed instead by PKCE and redirect URI checks |
client_secret_basic and client_secret_post are like showing a shared password at the door — simple, but the password itself has to be transmitted and protected everywhere it’s used. private_key_jwt and mTLS are more like proving you own a specific signature or a specific physical key — nobody ever has to hand over the actual secret to prove they have it, which is generally considered stronger.
Public clients use the “none” method not because authentication doesn’t matter, but because there’s genuinely nothing secret left to authenticate with. Instead of proving who the client is through a credential, the system leans on other tools — PKCE to prove continuity within one specific flow, and a pre-registered, tightly checked redirect address to ensure tokens only ever reach the legitimate app instance.
Some teams building a mobile or single-page app still request client_secret_basic or client_secret_post credentials and embed them in the app anyway, treating the client as if it were confidential. Because the secret is trivially recoverable from the shipped app, this provides a false sense of security rather than real protection.
Choosing between these methods in practice usually comes down to how much operational effort a team is willing to invest versus how sensitive the data behind the integration is. client_secret_basic and client_secret_post are simple to implement and widely supported, which makes them the default choice for most confidential clients. private_key_jwt and mTLS require managing cryptographic key pairs or certificates, which adds real operational overhead — generating, distributing, and eventually rotating keys — but removes the risk of a shared secret ever being transmitted or stored in a form that could be copied wholesale. Organizations handling especially sensitive data, such as financial transactions or health records, frequently mandate the stronger methods specifically because they eliminate that shared-secret exposure entirely.
There’s also a practical difference in how each method behaves when something goes wrong. With client_secret_basic or client_secret_post, a leaked secret can be used by an attacker for as long as it remains valid, with no way to distinguish the attacker’s requests from the legitimate application’s own requests — both simply present the same value. With private_key_jwt, the client signs a fresh statement for every single request rather than transmitting a reusable secret, so even observing one signed request doesn’t hand an attacker anything they could replay later. This difference is why security-conscious teams increasingly favor signature-based methods for their most sensitive confidential clients, even though the simpler shared-secret methods remain perfectly adequate for lower-risk integrations.
6Architecture: Where Client Type Changes the Flow
The four core OAuth roles stay the same regardless of client type — but the path a request takes, and who holds what, shifts noticeably.
flowchart TD
subgraph Confidential["Confidential Client Architecture"]
U1["Resource Owner"] --> B1["Backend Server
(holds client secret)"]
B1 -- authenticated token request --> AS1["Authorization Server"]
B1 -- access token --> RS1["Resource Server"]
end
subgraph Public["Public Client Architecture"]
U2["Resource Owner"] --> B2["Mobile App / SPA
(no secret, uses PKCE)"]
B2 -- unauthenticated + PKCE proof --> AS2["Authorization Server"]
B2 -- access token --> RS2["Resource Server"]
end
In the confidential architecture, a backend server sits between the end user and the Authorization Server for the sensitive parts of the exchange, and that backend is the only place the client secret ever lives. In the public architecture, there is no such intermediary — the mobile app or browser talks directly to the Authorization Server, presenting a PKCE proof instead of a secret. Both architectures still produce an access token that gets sent to the Resource Server in the end; the difference is entirely in how the client proves itself along the way.
Many real systems use a mixed architecture: a single-page app (public client) talks to its own backend (confidential client), and that backend is the one that actually holds tokens and calls external APIs. This “backend-for-frontend” pattern, introduced in earlier tutorials as a best practice, effectively converts a public-client problem into a confidential-client one by moving sensitive work off the browser entirely.
7Internal Working: How a Confidential Client Exchanges Tokens
Here’s the token exchange step specifically from a confidential client’s point of view — the part of the flow where its secret actually gets used.
User completes login and consent
Just like any OAuth flow, the user is redirected to the Authorization Server, logs in, and approves the requested scopes.
Browser returns with an authorization code
The user’s browser is redirected back to the application with a short-lived, single-use code attached.
Backend server receives the code
Because this is a confidential client, the code lands on the backend server — not directly in front-end code the user’s browser could tamper with.
Backend authenticates itself while exchanging the code
The server sends the code to the Authorization Server along with its client secret (or a stronger credential like private_key_jwt), proving it truly is the registered application.
Authorization Server verifies both the code and the client
Only if the code is valid and the client authentication succeeds does the Authorization Server release tokens.
Tokens stay server-side
The access and refresh tokens are stored on the backend, often never sent to the user’s browser at all, closing off an entire category of browser-based theft.
sequenceDiagram
participant User as Resource Owner
participant Backend as Confidential Client (Backend)
participant AS as Authorization Server
participant RS as Resource Server
Backend->>AS: Redirect user with scopes
AS->>User: Show login and consent screen
User->>AS: Approve requested scopes
AS->>Backend: Redirect back with authorization code
Backend->>AS: Exchange code + client secret for tokens
AS->>Backend: Return access token and refresh token
Backend->>RS: API request with access token
RS->>Backend: Return requested data
Notice that the client secret is used exactly once per token exchange — at step four — and only ever travels between the backend server and the Authorization Server, never through the user’s browser. This is precisely what makes it viable: the secret’s confidentiality depends on it staying inside infrastructure the developer controls from beginning to end.
8Internal Working: How a Public Client Exchanges Tokens with PKCE
Public clients follow a similar shape, but replace “prove you know the secret” with “prove you’re the same app that started this flow.”
App generates a one-time PKCE secret
Before redirecting the user anywhere, the mobile app or SPA generates a random value that only it knows, called a code verifier.
App sends a scrambled version upfront
A one-way transformed version of that value, called a code challenge, is included in the initial redirect to the Authorization Server — the original value is never sent yet.
User logs in and approves scopes
Identical to any other flow — the Authorization Server handles authentication and consent directly with the user.
App receives the authorization code
The Authorization Server redirects back to the app with a one-time code, just as with a confidential client.
App reveals the original secret to redeem the code
When exchanging the code for tokens, the app now sends the original, un-scrambled code verifier. The Authorization Server checks it against the code challenge from step two.
Match confirms continuity, tokens are released
If the values match, the Authorization Server knows whoever is redeeming the code is the same party who started the flow — not an attacker who merely intercepted the code along the way.
sequenceDiagram
participant User as Resource Owner
participant App as Public Client (Mobile/SPA)
participant AS as Authorization Server
participant RS as Resource Server
App->>App: Generate code verifier + code challenge
App->>AS: Redirect user with scopes + code challenge
AS->>User: Show login and consent screen
User->>AS: Approve requested scopes
AS->>App: Redirect back with authorization code
App->>AS: Exchange code + code verifier for tokens
AS->>App: Return access token (and refresh token, if allowed)
App->>RS: API request with access token
RS->>App: Return requested data
Unlike a client secret, the PKCE code verifier is generated fresh for every single login attempt and discarded immediately afterward. Even if someone eventually recovered a used code verifier, it would be worthless — that specific flow has already finished, and the next login will use a brand-new, unrelated value.
9Data Flow and the Client Registration Lifecycle
Before any of the flows above can run, an application has to be registered with the Authorization Server — and that registration is where its client type is locked in.
flowchart TD
A["Developer registers a new application"] --> B{"Can the app keep a secret confidential?"}
B -- Yes --> C["Registered as Confidential Client"]
B -- No --> D["Registered as Public Client"]
C --> E["Issued Client ID + Client Secret"]
D --> F["Issued Client ID only"]
E --> G["Secret stored securely, rotated periodically"]
F --> H["App relies on PKCE + redirect URI checks per flow"]
Registration typically happens once, when a developer creates a new application entry in the Authorization Server’s dashboard or through an automated process called dynamic client registration. At that point, the developer (or an automated policy) declares the client’s type, its allowed redirect addresses, and which scopes it may request. From then on, every login attempt from that application is checked against these registered settings.
For confidential clients, the lifecycle doesn’t end at registration — client secrets are typically rotated periodically, meaning a new secret is issued and the old one retired on a schedule, or immediately if a leak is suspected. This is straightforward because the secret lives in infrastructure the developer controls and can update centrally. Public clients have no equivalent secret to rotate; instead, their ongoing security depends on keeping the app itself updated, since a security fix to the PKCE implementation, for instance, only reaches users once they update the app.
Because a client secret can be rotated centrally without users doing anything, teams sometimes assume the same is true for anything embedded in a public client. It isn’t. Anything shipped inside a mobile app or browser bundle is effectively frozen until users install an update, which is exactly why public clients should never depend on an embedded secret in the first place.
Dynamic client registration adds another layer worth understanding. Rather than a developer manually filling out a form in an Authorization Server’s dashboard, some systems allow applications to register themselves programmatically at runtime — useful for platforms where many independent client instances need their own registration, such as an enterprise software product installed separately by hundreds of customer organizations. Even in this automated case, the confidential-versus-public decision still has to be made explicitly as part of the registration request; automation changes how the decision is recorded, not whether it needs to be made.
10Security: Why the Two Client Types Need Different Defenses
Confidential and public clients aren’t just handled differently by convention — each faces a genuinely different threat model.
Secret Leakage in Source Control
The most common real-world failure for confidential clients is a secret accidentally committed to a public code repository, log file, or error report — not the cryptography failing.
Centralized Secret Storage
Storing secrets in a dedicated secrets manager rather than in code or configuration files, with strict access controls and rotation schedules.
Authorization Code Interception
On a shared device or through a maliciously registered app claiming the same redirect scheme, an attacker could intercept a legitimate authorization code meant for someone else’s app.
PKCE, Every Time
As detailed in Chapter 8, PKCE ensures an intercepted code is useless to anyone who didn’t also generate the matching code verifier, closing this gap without needing a secret.
Loose Redirect URI Matching
Both client types are vulnerable if the Authorization Server accepts redirect addresses too loosely — allowing wildcards or partial matches can let an attacker redirect codes or tokens to their own server.
Exact Redirect URI Matching
Registering and enforcing exact, complete redirect addresses — no wildcards — regardless of whether the client is confidential or public.
Because a public client cannot authenticate itself, some Authorization Servers restrict or shorten refresh token lifetimes for public clients, or require re-authentication more often, precisely because there’s one less layer of proof available compared to a confidential client.
It’s worth stepping back to notice what these defenses have in common: none of them depend on the client type staying secret from an attacker. An attacker can know perfectly well that a given app is a public client, know its Client ID, and even see its entire source code — and still be unable to complete a login on someone else’s behalf, because PKCE ties each flow to the specific device that started it, and the redirect URI check ensures results only ever land back with the legitimate app. This is a deliberate design principle: OAuth’s security for public clients was built to hold up even under the assumption that the attacker already knows everything about the client’s own code.
11Advantages, Disadvantages, and Trade-offs
Neither client type is simply “better” — each fits a different kind of application, with its own costs.
Confidential Clients — Advantages
- Strong client authentication adds a real extra layer of proof beyond just the user’s login.
- Tokens and secrets can be kept entirely off the end user’s device.
- Secrets can be centrally rotated without requiring any user action.
- Supports the widest range of authentication methods, including the strongest ones like mTLS.
Confidential Clients — Trade-offs
- Requires operating and securing backend infrastructure, which not every application has or needs.
- Adds an extra network hop between the user’s device and the final API call.
- A misconfigured or careless deployment can still leak a secret, undermining the whole model.
Public Clients — Advantages
- No backend infrastructure required just to handle authentication, simplifying architecture for purely client-side apps.
- Works naturally for installed apps and devices that were never going to have a private server component.
- PKCE provides strong protection against code interception without needing any stored secret at all.
Public Clients — Trade-offs
- Cannot use client authentication, relying entirely on PKCE and redirect checks instead.
- Tokens often have to be stored on the end-user’s device, which is a less controlled environment.
- Security fixes require users to update the app itself, rather than a silent server-side change.
12Monitoring, Logging, and Metrics
Because the two client types face different risks, the signals worth watching differ slightly between them too.
Client Authentication Failure Rate
For confidential clients, a spike in failed client authentication attempts can indicate a rotated secret that wasn’t updated everywhere, or an attacker guessing credentials.
PKCE Verification Failure Rate
For public clients, a rise in failed code verifier checks can point to an interception attempt, a bug in the app’s PKCE implementation, or a client clock issue.
Requests Missing PKCE Entirely
Authorization Servers that enforce PKCE for public clients can track and alert on registered public clients that attempt a flow without a code challenge at all, catching outdated app versions.
Secret Age and Rotation Compliance
Tracking how long each confidential client’s secret has gone without rotation helps enforce an organization’s own security policy over time.
Redirect URI Mismatch Attempts
Counting requests rejected for using an unregistered or malformed redirect address, which can reveal both misconfiguration and active probing by attackers.
Token Issuance by Client Type
Comparing overall token issuance volume between confidential and public clients helps teams understand which parts of their ecosystem carry the most exposure if something goes wrong.
Client secrets, private keys, and PKCE code verifiers should never appear in plain text in logs, error messages, or crash reports — the same discipline that applies to access and refresh tokens applies equally here.
13Design Patterns and Anti-Patterns
Some ways of handling client type reflect solid engineering; others reintroduce exactly the risk this whole classification exists to avoid.
Problem
Embedding a client secret inside a mobile app or single-page application’s shipped code, treating it as if the app were a confidential client.
Why It’s Harmful
The secret can be extracted by anyone who downloads or inspects the app, giving a false sense of protection while providing essentially none — the value is public the moment the app is distributed.
Correct Approach
Register the app as a public client, drop the secret entirely, and rely on PKCE plus strict redirect URI validation instead.
Problem
A backend service storing its confidential client secret in the same source repository as its application code, unencrypted.
Why It’s Harmful
Anyone with repository access — including former employees, misconfigured public repositories, or compromised developer accounts — can read the secret directly, defeating the entire purpose of a confidential client.
Correct Approach
Store secrets in a dedicated secrets manager, injected into the running application only at deploy time, and never committed to source control.
Problem
A single-page application needs to call protected APIs, but has no secure place to hold long-lived tokens or a secret.
Approach
Introduce a lightweight backend-for-frontend component — a confidential client — that performs the token exchange and holds tokens server-side, issuing the browser only a short-lived session reference instead of the raw tokens themselves.
Correct Approach
This converts what would otherwise be a purely public-client problem into a well-understood confidential-client architecture, at the cost of a small additional backend component.
14Best Practices and Common Mistakes
A condensed checklist for treating client type as a first-class design decision rather than an afterthought.
Classify Honestly at Design Time
Decide confidential versus public based on where the code genuinely runs, not on how convenient one option would be.
Always Enable PKCE for Public Clients
Treat it as mandatory rather than optional, even if an Authorization Server technically allows skipping it.
Rotate Confidential Client Secrets on a Schedule
Don’t wait for a suspected leak — regular rotation limits how long any single leaked secret stays useful.
Reuse One Client Registration Across Platforms
A mobile app and its companion backend should typically be registered as two separate clients, each correctly classified, rather than sharing one registration that blurs the distinction.
Assume Minification Equals Protection
As covered in Chapter 1, obfuscated code is still fully public code — never rely on it to hide a genuine secret.
Skip Redirect URI Strictness for “Just a Prototype”
Loose settings adopted for early convenience have a habit of quietly surviving into production, where they become a real vulnerability.
When reviewing a new integration, explicitly ask and document “is this client confidential or public, and why?” as part of the design review — not just as an implementation detail decided later by whoever writes the login code.
It also helps to revisit this classification whenever an application’s architecture changes, not just when it’s first built. A tool that started as a simple command-line script talking directly to an API might later grow a companion web dashboard with its own backend — at that point, the original command-line client may still be public, but the new dashboard component deserves its own, separate confidential-client registration rather than inheriting settings meant for a different kind of application. Treating client classification as a one-time decision made at project kickoff, rather than something revisited as the system evolves, is one of the quieter ways teams end up with a mismatch between an app’s real environment and how it’s been configured to authenticate.
15Real-World and Industry Examples
This split shows up constantly once you know to look for it — often as the quiet reason two similar-looking apps are built so differently underneath.
A banking app’s mobile client versus its internal core-banking service
The mobile app the customer installs is a public client, relying on PKCE and the phone’s own secure storage for tokens. The core-banking service it eventually talks to, deep inside the bank’s own infrastructure, is a confidential client using strong authentication like mTLS between internal systems.
A SaaS product’s web dashboard
Often implemented with a confidential-client backend handling login and token storage, while the dashboard’s front-end JavaScript only ever receives a session cookie — never the underlying OAuth tokens themselves.
A developer tool’s command-line interface
Registered as a public client using PKCE, often opening a browser window for the user to log in and redirecting back to a temporary local address on the developer’s own machine to receive the authorization code.
A payment processor’s server-to-server integration
A textbook confidential client using the Client Credentials grant, authenticating with a private key rather than a shared secret, since no end user is involved and the highest possible assurance is worth the added setup.
A smart home hub connecting to a cloud service
Often registered as a public client despite being a physical device rather than an app someone installs, since a determined attacker with physical access to the hardware could eventually extract anything stored on it — the same underlying assumption that applies to phones and laptops applies here too.
16Frequently Asked Questions
Not as a single registration, but an application’s different components often are registered separately — for example, a mobile app (public) and its backend service (confidential) are typically two distinct client registrations working together.
PKCE was originally designed for public clients, but modern guidance recommends using it for confidential clients too, as an extra layer of protection against authorization code interception — it doesn’t replace client authentication, it complements it.
Nothing dangerous on its own — the Client ID was never meant to be secret in the first place. It only identifies which app is making a request; actual protection for public clients comes from PKCE and redirect URI validation, not from hiding the Client ID.
Many Authorization Servers do issue refresh tokens to public clients, but often with extra safeguards like rotation on every use and shorter overall lifetimes, precisely because there’s no client authentication backing them up the way there is for confidential clients.
No. HTTPS protects data while it travels over the network, but it does nothing to protect a value once it has already arrived and been installed on the user’s own device, where the app’s code itself can be inspected directly.
Requiring a backend for every application would be impractical — many useful apps, like offline-capable mobile tools or simple browser extensions, are genuinely meant to run entirely on the user’s device. OAuth’s public-client model exists precisely to support these cases safely, rather than forcing unnecessary infrastructure on every developer.
17Summary and Key Takeaways
OAuth 2.0’s confidential-versus-public split comes down to one honest question: can this application’s environment actually protect a secret, or not? Backend servers, running entirely inside infrastructure a developer controls, can — and are trusted with client secrets, stronger authentication methods, and server-side token storage. Mobile apps, browsers, and other user-controlled environments generally cannot — and instead lean on PKCE, strict redirect URI matching, and careful token handling to stay safe without ever needing a secret that couldn’t really stay secret. Neither category is inherently more trustworthy or more secure; each is simply matched to a different set of real-world constraints, and understanding which one an application belongs to is one of the first and most consequential decisions in designing any OAuth integration.
Key Takeaways
- Confidential clients can keep a secret; public clients can’t. — The distinction is architectural, not a measure of trust or importance.
- Client type is decided at registration and shapes everything after. — It determines allowed authentication methods, PKCE requirements, and token storage strategy.
- Confidential clients authenticate; public clients prove continuity instead. — Methods like client_secret_basic, private_key_jwt, and mTLS require a secret; PKCE requires none.
- Obfuscation is not protection. — Anything embedded in a shipped app or browser bundle should be treated as public information.
- The backend-for-frontend pattern bridges the gap. — It lets a public-facing app benefit from confidential-client protections by moving sensitive logic to a server.
- Both risks are real, just different. — Confidential clients mainly guard against secret leakage; public clients mainly guard against code interception.
- The right classification is the safe one. — Misclassifying a public client as confidential creates a false sense of security that’s worse than acknowledging the app cannot keep a secret.