OAuth Clients
Register an application, configure its redirect URIs, and manage its credentials.
OAuth Clients
An OAuth client is the record that lets an application sign users in through Aether Labs SSO. It holds a name, one or more redirect URIs, the scopes the app may request, and a secret.
You only need one for Pattern B. First-party apps on *.aetherlabs.agency
use cookie SSO and need no client at all.
Create a client
From the SSO admin console at admin.sso.aetherlabs.agency → OAuth Clients, or
with the API:
POST https://sso.aetherlabs.agency/api/admin/oauth-clients
Authorization: Bearer <admin access token>
Content-Type: application/json
{
"name": "Your Product",
"redirect_uris": ["https://yourapp.example.com/oauth/callback"],
"scopes": ["openid", "profile", "email"],
"is_active": true
}Requires the oauth_clients:write permission. The response contains client_id
and — once, never again — client_secret.
Redirect URIs
- Matching is exact: scheme, host, port, path and query must all be identical to a registered value. No wildcards, no prefix or suffix matching.
- Production callbacks must be
https.http://localhostis accepted for local development. - Register every URI you will use — a production one and a dev one.
- An unregistered URI is refused at
/authorizewithinvalid_request; a mismatch at/tokenfails withinvalid_grant.
Localhost URIs do not work in production
A client whose only redirect URI is http://localhost:… cannot complete the
flow in production. Check this first if authorize returns invalid_request.
Manage an existing client
| Action | Endpoint | Permission |
|---|---|---|
| List / read | GET /api/admin/oauth-clients, GET /api/admin/oauth-clients/:clientId | oauth_clients:read |
| Update | PUT /api/admin/oauth-clients/:clientId | oauth_clients:write |
| Rotate secret | POST /api/admin/oauth-clients/:clientId/rotate-secret | oauth_clients:rotate_secret |
| Deactivate | DELETE /api/admin/oauth-clients/:clientId | oauth_clients:delete |
Rotating a secret immediately invalidates the old one: the app stops
authenticating at /token until it is redeployed with the new value.
Deactivating a client makes /authorize treat it as unknown.
Handling the secret
- Stored bcrypt-hashed; the plaintext exists only in the creation/rotation response.
- Keep it server-side. Never in a
VITE_*,NEXT_PUBLIC_*, or any browser bundle. - The token and revocation endpoints authenticate the client with it, either as
HTTP Basic (
Authorization: Basic base64(client_id:client_secret)) or asclient_id+client_secretin the request body.
Public clients are not supported
Every client is treated as confidential: a secret is required at the token endpoint. PKCE alone cannot stand in for it.
A browser-only app (SPA) therefore cannot hold the secret — it must run the authorization-code exchange on its own backend and issue its own session cookie (a BFF). That backend is also where the refresh token lives, which keeps it out of JavaScript entirely.
Next
- Scopes & tokens — what a client may ask for.
- OAuth integration — the full flow, step by step.