OAuth Clients
Register an OAuth client so your app can request tokens via Hosted Login or any OIDC-compatible flow, or a machine client so one of your own systems can get tokens for itself.
Overview
An OAuth client in Canopy is a registered OAuth 2.0 credential: the client_id + client_secret pair your app uses to request tokens on behalf of an end user. Distinct from the platform's Application concept: in the Account → Application → Environment hierarchy, an Application is the unit your access-control model lives under; an OAuth client is configuration that lives inside a single Environment. You'll typically register one OAuth client per Environment (e.g. one in development, one in staging, one in production). From each registration you receive a client_id (a public UUID) and a client_secret (a backend-only credential, shown once at creation). Both values feed into the OAuth flows covered on the Authentication page, where Hosted Login uses them to exchange an authorization code for an access token, ID token, and refresh token. A user signing in through a given OAuth client always authenticates into that client's Environment. There are two kinds. A web client, the default, signs people in: the token it receives is that person's. A machine client is a principal of its own: it holds roles at your nodes, acts inside at most one organization, and obtains a token for itself with the client_credentials grant, with no person present. Machine clients are described on Machine Clients; the rest of this page is about web clients unless it says otherwise.
Register from the Console
Registering from the Developer Console is the fastest path. The form mirrors the API surface but applies sensible defaults so you only fill in what's specific to your app.
- Application
- Developer Console
- Path
- Access ControlIntegrationsOAuth Clients
- Opens with
oauth_clients.viewRead the Environment's OAuth clients.
- Context
- Select an Application and an Environment in the switcher first; the page belongs to that Environment.
client_id and client_secret. Copy the secret to your secrets manager before clicking Done; it is shown only once.If you don't see this
Each control on the OAuth Clients page exists only for someone who may use it. When something described above is missing, one of these is why:
Register via the API
If you'd rather provision OAuth clients from a script or CI pipeline, the same operations are on the public API. The Environment comes from the API key: a key is issued for one Environment, so register with a key from the Environment the client should live in. A scoped key needs oauth_client.manage to create, update, delete, or rotate, and oauth_client.view to read; a full-access key holds both. The same endpoints also exist on the portal surface (/portal/v1/accounts/:accountSlug/applications/:appSlug/environments/:envSlug/oauth-clients) for a signed-in administrator's Bearer JWT.
POST /api/v1/oauth-clientsRequest
curl -X POST https://auth.canopy-io.com/api/v1/oauth-clients \ -H "X-API-Key: $CANOPY_API_KEY" \ -H "Content-Type: application/json" \ -H "Idempotency-Key: 6f1e2a3b-create-production-client" \ -d '{ "name": "My App (production)", "redirect_uris": ["https://app.example.com/auth/callback"] }'
Response
{ "data": { "client_id": "0d4b7e90-…", "client_secret": "e3b0c44…", "name": "My App (production)" } }
The client_secret in the response is the only time you'll see it in plaintext. Send an Idempotency-Key header and a retried create returns the same client and secret for 24 hours instead of minting a second client; the same key sent with a different request body is refused with 422 idempotency.key_reused_with_different_request, so a key names one request, never a slot to reuse. The data envelope is the standard shape for every endpoint that returns a single resource.
Configuration fields
Every OAuth client is configured with the same handful of fields: required ones are enforced by the API, optional ones fall back to sensible defaults.
| Field | Required | Default | Description |
|---|---|---|---|
name | Yes | None | A human-readable label for the OAuth client, shown in the Console list, never returned in tokens. Make it descriptive enough to identify in a list (e.g. Mobile app (prod), Internal admin). |
client_kind | No | web | web: a person signs in through the client with the authorization code grant. machine: the client holds roles of its own and obtains tokens for itself with client_credentials; it takes organization_node_id and allowed_audiences and no redirect URIs or sign-in fields. Set at registration; shown as Kind in the Console. |
redirect_uris | Yes, for a web client | None | Array of URLs (at least one). Every URL your app may redirect to after Hosted Login completes. The match is exact: no wildcards, no trailing-slash forgiveness. URIs must be http/https URLs (custom or native-app schemes like myapp://callback aren't accepted), and a public TLD is not required, so a localhost callback is valid during development. A machine client has none and is refused any (oauth.machine_client_fields_invalid). |
post_logout_redirect_uris | No | [] | Where this client may send someone after an RP-initiated logout. Matched exactly, and kept separate from redirect_uris: receiving an authorization code and being a landing page after sign-out are different kinds of trust. Edited in the Console as Post-logout redirect URLs. |
scopes | No | ["openid", "profile", "email"] | OIDC scopes the OAuth client is allowed to request, each one of openid, profile, email, org and permissions; any other value is refused with 400 oauth.client_scope_unknown. The Console form omits this and uses the default, openid profile email; the API accepts an explicit array. Add org to include account_id, application_id, and environment_id in the ID token. A machine client may carry permissions only, and gets it when scopes is omitted. |
backchannel_logout_uri | No | None | Where Canopy POSTs a signed logout token, server to server, when a session this client issued tokens for ends. Omit it and the client is told nothing. Edited in the Console as Back-channel logout URL. |
invite_redirect_url | No | None | Where to send a user after they accept an invite issued through this OAuth client. Without one, an invite that names this client is refused with oauth.client_no_invite_url; an invite that names no client lands on the Environment's auth-link origin, else Canopy's hosted page. |
initiate_login_uri | No | Environment's sign-in URL, if set | The page in your app that starts a sign-in. Canopy's hosted pages link here as Sign in after someone accepts an invitation, verifies their email or resets their password. https (http only on localhost), no fragment. Shown as Sign-in URL in the Console. |
organization_node_id | No | None | Machine clients only. The one organization the client acts in: its tokens name it and it may hold roles only inside it. Omitted, the client acts across the Environment. Must be a live organization of the Environment (oauth.client_organization_invalid otherwise). Set at registration and never changed; shown as Organization in the Console. |
allowed_audiences | No | [] | Machine clients only. The audiences the client may ask a token for, beside the Environment's default audience (set on the Authentication page). A token request naming any other is refused with invalid_target. Up to 20 counts values, each a single token with no spaces. Shown as Allowed audiences in the Console; a web client sending it is refused (oauth.web_client_audiences_invalid). |
What you receive
Two values are returned at creation time. They live in different places and have very different security properties.
| Field | Visibility | Description |
|---|---|---|
client_id | Public | A UUID. Safe to embed in your frontend, in URL query strings, in logs. Identifies your OAuth client to Canopy on every authorization request. |
client_secret | Backend-only | A high-entropy random string. Stored hashed at rest by Canopy and never returned again after creation. Used by your backend to authenticate the code-for-token exchange at /oauth/token. |
The client_secret is shown only once, at creation, in the registration dialog or API response. Canopy stores a bcrypt hash and there is no way to retrieve the plaintext later. If you miss it or it's compromised, mint a fresh one. In the Console, open the client from Integrations → OAuth Clients and click Rotate secret; via the API, call POST /api/v1/oauth-clients/{client_id}/rotate-secret with an API key holding oauth_client.manage (the same route exists on the portal surface under the Environment). Either way the new secret is shown once and the old one stops working immediately.
Manage OAuth clients
Beyond create, the public API (/api/v1/oauth-clients, each client named by its client_id) and the portal API support listing, a summary of active and inactive counts, fetching, updating metadata, rotating the secret, and deleting OAuth clients. PATCH can change the name, redirect URIs, post-logout redirect URLs, scopes, back-channel logout URL, invite redirect URL, sign-in URL and is_active, but not the secret. To rotate a compromised secret, call POST .../oauth-clients/:id/rotate-secret; the client_id is unchanged and the new secret is returned once. Full details on each endpoint are in the API Reference. A machine client's drawer also lists the roles it holds, under Roles, with Add role, change and remove; see Machine Clients.
Tell us how we can improve this guide.