Public and confidential clients

OAuth 2.0 Course · lesson 9 of 13 · 5 min read

Whether your client can keep a secret decides the whole flow.

Open this lesson in the learning hub

Key points

  • A confidential client can keep a secret - a server-side application where the secret lives in configuration the user never sees.
  • A public client cannot. A single-page app, a mobile app or a desktop app ships its code to the user, so anything embedded in it is readable.
  • This is not a matter of care. Minified JavaScript can be read, and a mobile binary can be decompiled - a secret in either is public by definition.
  • That is why a public client must use PKCE and must never use the client credentials grant or embed a client secret.
  • The distinction also decides token storage: a confidential client can hold a refresh token safely, while a public client needs rotation and short lifetimes to compensate.
  • A backend-for-frontend turns a public client into a confidential one: the browser talks to your server with a cookie, and your server holds the tokens.

Example

# CONFIDENTIAL - server-side, holds a real secret
#
#   Spring Boot app, Django app, any backend
#   client_id     + client_secret
#   authorization_code, or client_credentials for machine-to-machine
#   can store refresh tokens safely

# PUBLIC - ships to the user, cannot hold a secret
#
#   SPA, mobile app, CLI, desktop app
#   client_id ONLY
#   authorization_code + PKCE, always
#   refresh tokens must be rotated and short-lived

---
# THE BFF PATTERN - the usual answer for a browser app.
#
#   browser  --session cookie-->  your backend  --tokens-->  API
#            (httpOnly, Secure)   (confidential client)
#
#   The browser never sees a token at all. XSS cannot steal what is
#   not there, and logout works because the session is revocable.

---
# Spring, as a confidential client:
spring:
  security:
    oauth2:
      client:
        registration:
          google:
            client-id: ${GOOGLE_CLIENT_ID}
            client-secret: ${GOOGLE_CLIENT_SECRET}   # env, never committed
            scope: openid,profile,email
            authorization-grant-type: authorization_code

# A public client config has NO client-secret line. If you find yourself
# wanting to add one to a SPA, you want a BFF instead.

A client that ships to the user cannot hold a secret - use PKCE, or move the tokens behind a backend.

This is a reading copy. The full lesson — with the visual explainer, the interactive lab and a Run button for the code — lives in the OAuth 2.0 Course course, and every lesson in it is listed on the OAuth 2.0 Course contents page.