Public vs Confidential Client
logi supports both client types from OAuth 2.0 RFC 6749 §2.1. The type you choose at RP registration determines both your security model and your implementation complexity.
One-line summary
| Question | Answer |
|---|---|
| Do you have a backend server that can store a client_secret safely? | → Confidential |
| Static SPA + serverless functions (Vercel/Netlify/Cloudflare) doing the token exchange? | → Confidential (the secret lives in the function's env) |
| A pure mobile app / SPA / CLI tool that cannot store a secret? | → Public |
| Both? (mobile + backend) | → Confidential + BFF (recommended) |
"SPA == Public" is not true
Even if your frontend is an SPA, you are Confidential if there is server-side code (including serverless functions) running the token exchange. The secret goes in the function's environment variables, not in the browser bundle. You are Public only when the browser calls /oauth/token directly (zero server code). For the static SPA + serverless pattern, see → SPA + serverless (Vercel).
Decision tree
flowchart TD
A[Start RP registration] --> B{Server-side backend<br>exists?}
B -- Yes --> C{Also running a mobile/SPA<br>client?}
B -- No<br>(pure client) --> D[Public client]
C -- Yes --> E[Confidential + BFF<br>recommended]
C -- No<br>(called only from the server) --> F[Confidential]
D --> G[No client_secret issued<br>PKCE S256 enforced<br>refresh rotation required]
E --> H[mobile → backend → logi<br>logi tokens stay on the backend only<br>client_secret kept on the backend]
F --> I[server-to-server<br>HTTP Basic or form body]Security model differences
Confidential client
- ✅ Authenticates the client with a
client_secret(RFC 6749 §2.3) - ✅ HTTP Basic or form body authentication
- ✅ Can store logi tokens in the backend DB to rotate, revoke, and audit them
- ⚠️ If the
client_secretleaks, rotate it immediately (logi/developer/applications/:id/rotate_secret)
Public client
- ✅ Cannot authenticate itself → mitigates authorization code interception with PKCE S256 (RFC 7636)
- ✅ Automatic refresh token rotation + family detection (the whole family is revoked when reuse is detected)
- ⚠️ logi tokens are exposed in client memory/storage → DPoP / keychain recommended
- ⚠️ A request is rejected if HTTP Basic or a client_secret parameter is found (downgrade defense)
- ⚠️
redirect_uriis allowed only for https / loopback / custom-scheme (RFC 8252 §8.5)
When to recommend a BFF (Backend-for-Frontend)
If you have a mobile app and also run your own backend, Confidential + BFF is almost always more secure.
BFF flow:
[Mobile] Start PKCE → call the authorize URL → receive code at the callback
│
▼
[Mobile] POST /api/auth/logi/exchange { code, code_verifier }
│
▼
[Backend] (holds client_secret) POST {logi}/oauth/token
│ Receive logi tokens → verify id_token → match the User
│ → Issue your own JWT
▼
[Mobile] Stores only your own JWT. It never sees logi tokens.Security gains of a BFF:
- The logi access_token / refresh_token never reaches the mobile device → even on device compromise, logi resources stay inaccessible
- The backend independently manages the TTL/rotation/revocation of its own tokens
- On token expiry, the backend silently refreshes with logi while the mobile app only renews its own JWT
When you don't need a BFF:
- A pure SPA / mobile app with no backend of its own (e.g. a widget that is self-contained on the client)
- Tokens used once on the device right after receipt (e.g. one-time pairing)
Security policies logi enforces on Public clients
| Item | Enforcement |
|---|---|
| PKCE | S256 required, plain rejected |
code_challenge / code_verifier | Required |
| HTTP Basic auth | Rejected (invalid_client) |
client_secret parameter | Rejected (invalid_client) |
redirect_uri scheme | https / 127.0.0.1 / localhost / custom-scheme |
External plaintext http redirect_uri | Rejected |
| Refresh token rotation | A new refresh token is issued on every response |
| Refresh token reuse detection | The whole family is revoked |
Changing your choice later
Once decided, it cannot be changed — you must register a new RP. This is an intentional constraint. Because the token policies of Public and Confidential differ, changing the type midway can break the security model of tokens that were already issued.
Related documents
- Public Clients technical guide — registration steps + PKCE examples in multiple languages
- Flutter Integration — implementing both public and BFF in Flutter
- PKCE — RFC 7636 details
- redirect_uri security — validation rules