Clients and API Scopes
In OAuth 2.0, a client is any application that requests access on behalf of a user or itself. API scopes define what operations a client is allowed to perform. The Identity Service manages both per tenant.
Client Types
OctoMesh supports three client types, each designed for a different use case:
Authorization Code Client
For web applications where a user interacts through a browser. Uses the Authorization Code flow with PKCE.
octo-cli -c AddAuthorizationCodeClient -id "my-web-app" -n "My Web Application" -u "https://myapp.example.com/" -ru "https://myapp.example.com/callback"
Characteristics:
- User authenticates via browser redirect
- PKCE required (no client secret needed for public SPAs)
- Supports refresh tokens with
offline_accessscope - User claims included in tokens
Default example: octo-data-refinery-studio — the Data Refinery Studio web application.
Client Credentials Client
For service-to-service communication without user interaction. The service authenticates with its own client ID and secret.
octo-cli -c AddClientCredentialsClient -id "my-background-service" -n "Background Processor" -s "MyServiceSecret123"
Characteristics:
- No user context — tokens have no
subclaim - Authenticates with client ID + client secret
- Bypasses tenant authorization middleware (no
allowed_tenantscheck) - Used for background jobs, data pipelines, automated processes
Device Code Client
For devices and CLI tools that cannot open a browser directly. The user authenticates on a separate device.
octo-cli -c AddDeviceCodeClient -id "my-iot-device" -n "IoT Sensor Gateway" -s "DeviceSecret123"
Characteristics:
- Device displays a URL and code for the user to enter
- User authenticates on any browser-capable device
- Device polls for completion
- Supports
offline_accessfor long-lived refresh tokens
Default example: octo-cli — the OctoMesh command-line tool.
Default Clients
When the Identity Service is set up, these clients are created automatically:
| Client ID | Type | Purpose |
|---|---|---|
octo-cli | Device Code | CLI tool for administration |
octo-idenityServices-swagger | Authorization Code (PKCE) | Swagger UI for Identity API |
octo-data-refinery-studio | Authorization Code (PKCE) | Data Refinery Studio (auto-provisioned; RefineryStudioUrl defaults to https://localhost:4200) |
URI Sources and Lifecycle
Each entry in a client's RedirectUris, PostLogoutRedirectUris, and AllowedCorsOrigins lists carries a Source provenance marker that drives how the entry behaves across blueprint re-applies and operator config changes.
Source values
| Source | Owned by | Lifecycle | Typical use |
|---|---|---|---|
base | Blueprint seed | Rewritten on every blueprint version bump | Default URIs shipped with the service |
api | REST API (Studio / octo-cli / direct HTTP) | Survives every blueprint re-apply | Operator-added URIs |
overlay:<name> | Overlay cmdlet | Survives every blueprint re-apply | Per-machine or per-team overlays |
family:<name> | Family reconciler reading env config | Ephemeral — fully regenerated on every restart | Multi-URI dev/test environments |
Preservation across blueprint re-apply
When the Identity service restarts and re-applies the System.Identity.Bootstrap blueprint, the seed rewrites every base-sourced entry on the five service-managed clients (rtId range 660…30..34). The service captures every non-base URI before the apply and merges it back afterwards, so:
- A URI an operator added via Studio Client-UI (
apisource) survives the re-apply. - An overlay-cmdlet URI (
overlay:<name>source) survives the re-apply. - A URI the seed re-asserts with the same value wins on collision — the captured copy is dropped, the entry now reads
Source = "base".
Without this mechanism every restart would silently destroy operator-added URIs.
Choosing the right mechanism
URIs reach a blueprint-managed client through four paths. Pick by intent:
| Need | Mechanism | Where to author it |
|---|---|---|
| URIs that exist in every deployment of every environment | Blueprint base | System.Identity.Bootstrap/seed-data/entities.yaml — bump blueprint version |
| URIs that vary per environment but are deterministically generated from env config (e.g. one dev-server URL per family member, switched in cluster) | Family (family:<name>) | {{family.NAME}} placeholder in the seed + OCTO_IDENTITY__URIFAMILIES__* env config |
| Operator-blessed permanent additions on a managed environment (e.g. a one-off partner callback that should survive every restart) | REST API (api) | Studio Client-UI PATCH /v1/clients/... |
Developer / machine-local additions that should NOT leak into shared exports (e.g. http://localhost:4200/auth-callback) | Overlay (overlay:<name>) | octo-tools/overlays/<name>.yaml + Apply-IdentityOverlay |
If a URI changes shape per environment but exists everywhere, use family. If it is environment-independent and operator-driven, use overlay (machine-local) or api (managed-env permanent). If it is part of the deployable spec on every cluster, put it in the blueprint.
Family placeholders for multi-URI dev environments
The seed YAML can include a {{family.NAME}} placeholder in any of the three URI lists. At Identity startup the placeholder is replaced with one entry per registered family member from OctoIdentityServicesOptions.UriFamilies, tagged Source = "family:NAME". Use this when the same SPA runs on multiple ports in the same cluster — e.g. Angular dev server :4200 plus Vite preview :5173.
The octo-data-refinery-studio client ships with a {{family.local-dev}} placeholder in all three URI lists. Other service-managed clients can opt in by a one-line seed edit and a blueprint version bump.
Configuring a family
# Two members for the local-dev family
Set-Item -Path "env:OCTO_IDENTITY__URIFAMILIES__LOCAL-DEV__0" -Value "https://localhost:4200/"
Set-Item -Path "env:OCTO_IDENTITY__URIFAMILIES__LOCAL-DEV__1" -Value "https://localhost:5173/"
Set-Item is used instead of the $env: shorthand because PowerShell parses the - in LOCAL-DEV as a subtraction operator with the shorthand syntax.
Reconciliation contract
The reconciler reads the current env config on every restart and brings the DB state into line. A blueprint version bump is not required to propagate a config change:
| Env config change | DB result on next restart |
|---|---|
| New member added | New entry materialised with Source = "family:NAME" |
| Member removed | Entry deleted from DB |
| Family fully unconfigured | All family:NAME entries removed |
| No change | No DB write (idempotent — no log entry either) |
A {{family.NAME}} placeholder is the declarative signal "this list wants this family". After first expansion the signal is carried by the existing family:NAME entries alone — the reconciler reads both forms and treats them as equivalent intent. A family that has neither a placeholder nor any existing entries on a given list stays inert; the reconciler does not guess where to put it.
CORS origin normalisation
IdentityServer's ValidatingClientStore rejects CORS origins with a trailing slash. The reconciler strips trailing slashes when writing to AllowedCorsOrigins only — RedirectUris and PostLogoutRedirectUris keep their trailing slashes (which IdentityServer matches exactly). A single family member configured as https://localhost:5173/ therefore resolves to https://localhost:5173/ in the redirect lists and https://localhost:5173 in CORS, all from one env-config value.
Production defaults
Production clusters typically leave UriFamilies unconfigured. The placeholders resolve to zero entries, vanish from the list, and the URI configuration is exactly what the seed shipped — no per-environment surprises and no risk of dev-only URIs landing on production clients.
Applying overlay URIs
The Apply-IdentityOverlay PowerShell cmdlet (shipped in octo-tools) fans octo-cli -c ApplyClientOverlay across every client listed in a declarative YAML file. The endpoint dedupes by URI string (any source), appends new entries with Source = "overlay:<OverlayName>", and short-circuits the DB write + cache bust when nothing was added — so re-runs are true no-ops.
# Default — applies octo-tools/overlays/identity-local-dev.yaml
# to the active octo-cli context's tenant
Apply-IdentityOverlay
# Sanity check before applying
Apply-IdentityOverlay -DryRun
# Personal one-off overlay marked under its own name (so DumpTenant --clean
# strips them without touching the shared local-dev entries)
Apply-IdentityOverlay -OverlayFile ~/dev/gerald-laptop.yaml -OverlayName gerald-laptop
The shared octo-tools/overlays/identity-local-dev.yaml file is the canonical local-dev URI set for every blueprint-managed client. Ports are hardcoded against the Start-Octo allocation — no second template-substitution layer; one source of truth per overlay file.
overlayName: local-dev
clients:
- clientId: octo-data-refinery-studio
redirectUris:
- http://localhost:4200/auth-callback
- http://localhost:4200/silent-renew
postLogoutRedirectUris:
- http://localhost:4200/
allowedCorsOrigins:
- http://localhost:4200
- clientId: octo-cli
redirectUris:
- http://localhost:5000/callback
# … other blueprint-managed clients
Per-developer exceptions go into a separate file applied under a separate overlay name, so they can be filtered independently on export.
See the Apply-IdentityOverlay reference for the cmdlet's full parameter set and the octo-cli ApplyClientOverlay reference for the single-client invocation shape.
Stripping overlay URIs from tenant dumps
Before sharing a tenant dump (or committing it as Blueprint seed material), strip the operator-only overlay:* URI entries so they don't leak into other environments. The dump itself is a raw mongodump archive of the tenant DB, so the filter happens before the dump rather than inside it — the operator runs three separate commands.
# 1) Strip every overlay:* entry across every blueprint-managed client (destructive)
octo-cli -c CleanClientOverlays -y
# 2) Dump the now-clean tenant
octo-cli -c DumpTenant -tid meshtest -o ./meshtest-clean.dump
# 3) Optional: restore the canonical local-dev overlays (idempotent — no-op if you re-applied between steps)
Apply-IdentityOverlay
CleanClientOverlays is destructive but tightly scoped: only entries where Source starts with overlay: are removed. base, api, and family:* entries always survive. To target a single overlay name (e.g. drop a personal gerald-laptop overlay while keeping the shared local-dev set), pass -n <overlayName>:
octo-cli -c CleanClientOverlays -n gerald-laptop -y
The companion Apply-IdentityOverlay cmdlet is idempotent — it dedupes against existing URIs, so re-running after step 2 restores any local-dev entries that were stripped without disturbing entries that were re-applied between steps. For automated dump pipelines, the same three-step recipe runs from a script.
From the Data Refinery Studio
The same cleanup is available in the Studio UI. When you back up a tenant (Tenant Management → right-click a tenant → Backup), the backup dialog offers a Clean overlay URIs before export checkbox. Enabling it runs the CleanClientOverlays step against that tenant before the dump starts, so the produced backup is template-clean without leaving the browser. base, api, and family:* URIs are preserved exactly as with the CLI. See the Studio OAuth Clients — Overlay URIs and template-clean exports page for the operator-facing walkthrough.
From the MCP server
For AI-assistant / automation contexts, the identity overlay endpoints are also exposed as MCP tools: apply_client_overlay (append overlay URIs to a single client) and clean_client_overlays (strip overlay:* entries across every client; classified High risk, so it requires confirm=true). They mirror the octo-cli commands one-to-one.
Architecture note: an atomic octo-cli -c DumpTenant --clean flag was considered but deferred — the dump infrastructure (bot-services + engine MongoDB) sits across a different service boundary from the identity-services schema knowledge, and bridging would require new cross-service HTTP infrastructure. The standalone CleanClientOverlays command (and its Studio checkbox / MCP tool) is the deliberate alternative: a separate, explicit, opt-in step rather than a flag baked into the dump lifecycle. The engine has a CloneTenantToTempAsync primitive ready for the atomic path when octo-platform-services takes Phase 3 ownership of cross-cutting tenant orchestration.
Managing Clients
List, Update, Delete
# List all clients
octo-cli -c GetClients
# Update a client
octo-cli -c UpdateClient -id "my-web-app" -n "Updated Name" -u "https://new-url.com/"
# Delete a client
octo-cli -c DeleteClient -id "my-web-app"
Client Secrets
Client credentials and device code clients require secrets. Secrets are stored as SHA256 hashes.
# Create a new secret with expiration
octo-cli -c CreateApiSecretClient -cid "my-service" -e "2027-12-31" -d "Production secret"
# List secrets
octo-cli -c GetApiSecretsClient -cid "my-service"
# Delete a secret
octo-cli -c DeleteApiSecretClient -cid "my-service" -s "<sha256-value>"
Client Roles and Group Membership
A client can be assigned roles and group memberships with the same semantics as a user — so a machine-to-machine identity using the client_credentials flow can act as an authorized caller against role-protected endpoints (for example an HTTP-triggered pipeline). This complements scopes: a scope controls which API surface the token may reach, while a role controls authorization within that surface.
Role claims in the access token
When a client_credentials token is issued, the Identity Service resolves the client's effective roles — its directly assigned roles plus any roles inherited from group memberships (including nested groups) — and emits them as role claims in the access token. The claim shape is identical to a user token, so consumers such as the FromHttpRequest trigger node and the backend authorization middleware need no client-specific code path.
Only the client_credentials grant is affected. Other flows (authorization_code, device_code, refresh_token) already carry the signed-in user's role claims via the profile service.
Assigning roles directly
# Assign a role to a client (by role name)
octo-cli -c AddClientToRole -id "my-service" -r "DataAnalyst"
# Replace the full set of directly-assigned roles (by role id)
octo-cli -c UpdateClientRoles -id "my-service" -rids "<role-id-1>,<role-id-2>"
# Remove a role (prompts for confirmation; add -y to skip)
octo-cli -c RemoveClientFromRole -id "my-service" -r "DataAnalyst"
Assigning roles via groups (recommended)
As with users, the recommended approach is to manage permissions through groups. Add the client to a group and it inherits all of the group's roles. A client is added by its runtime ID (RtId, from GetClient):
octo-cli -c AddClientToGroup -id "<group-rtid>" -cid "<client-rtid>"
octo-cli -c RemoveClientFromGroup -id "<group-rtid>" -cid "<client-rtid>"
Both surfaces are also available in the Data Refinery Studio client form (Roles + Group Memberships) and as MCP tools. Client role/group assignments can additionally be expressed declaratively in the Identity blueprint seed, so they survive re-seeding.
API Scopes
API scopes control which operations a client can perform. A client must be granted a scope to include it in token requests.
Default API Scopes
OctoMesh uses a unified set of API scopes shared across all services:
| Scope | Access Level | Description |
|---|---|---|
octo_api | Full access | Read and write access to all OctoMesh APIs |
octo_api.read_only | Read only | Read-only access to all OctoMesh APIs |
Managing API Scopes
# List all scopes
octo-cli -c GetApiScopes
# Create a custom scope
octo-cli -c CreateApiScope -n "myAPI.admin" -dn "Admin Access" -d "Full administrative access" -e true
# Update a scope
octo-cli -c UpdateApiScope -n "myAPI.admin" -dn "Updated Name"
# Delete a scope
octo-cli -c DeleteApiScope -n "myAPI.admin"
Granting Scopes to Clients
Use AddScopeToClient to grant a client access to specific API scopes:
# Grant asset repository access
octo-cli -c AddScopeToClient -id "my-web-app" -n "octo_api"
# Grant read-only API access
octo-cli -c AddScopeToClient -id "my-web-app" -n "octo_api.read_only"
A client can only request scopes it has been granted. If a client requests a scope it does not have, the token will not include that scope.
API Resources
API resources group related scopes and can have their own secrets (for API introspection).
Default API Resources
| Resource | Display Name | Description | Scopes |
|---|---|---|---|
octoAPI | Octo API | Unified access to all Octo platform APIs | octo_api, octo_api.read_only |
Managing API Resources
# List all resources
octo-cli -c GetApiResources
# Create a resource with scopes
octo-cli -c CreateApiResource -n "myAPI" -dn "My Custom API" -d "Custom API" -s "myAPI.read,myAPI.write"
# Update a resource
octo-cli -c UpdateApiResource -n "myAPI" -dn "Updated API Name"
# Delete a resource
octo-cli -c DeleteApiResource -n "myAPI"
API Resource Secrets
API resources can have secrets for token introspection:
# Create a secret
octo-cli -c CreateApiSecretApiResource -n "myAPI" -e "2027-12-31" -d "Introspection secret"
# List secrets
octo-cli -c GetApiSecretsApiResource -n "myAPI"
# Delete a secret
octo-cli -c DeleteApiSecretApiResource -n "myAPI" -s "<sha256-value>"
Identity Resources
Identity resources define which user claims are included in identity tokens. OctoMesh configures these by default:
| Resource | Claims | Purpose |
|---|---|---|
openid | sub | Required for OIDC — user identifier |
profile | name, given_name, family_name | User profile information |
email | email, email_verified | User email address |
role | role | User roles (custom resource) |
When a client requests scope=openid profile email role, the resulting token includes all claims from these identity resources plus the OctoMesh-specific claims (tenant_id, allowed_tenants).
Example: Registering a New Web Application
Complete workflow for adding a new web application to OctoMesh:
-
Create the client:
octo-cli -c AddAuthorizationCodeClient -id "my-dashboard" -n "My Dashboard App" -u "https://dashboard.example.com/" -ru "https://dashboard.example.com/auth/callback" -
Grant required scopes:
octo-cli -c AddScopeToClient -id "my-dashboard" -n "octo_api" -
Verify the client:
octo-cli -c GetClients
The application can now authenticate users via Authorization Code + PKCE and call all OctoMesh APIs.
See Also
- octo-cli — AddAuthorizationCodeClient and related client commands (
AddClientCredentialsClient,AddDeviceCodeClient,GetClients,UpdateClient,DeleteClient,AddScopeToClient) — CLI commands for client management - octo-cli — CreateApiSecretClient and related secret commands (
GetApiSecretsClient,UpdateApiSecretClient,DeleteApiSecretClient) — manage client secrets from the CLI - octo-cli — AddClientToRole and related client role/group commands (
RemoveClientFromRole,UpdateClientRoles,AddClientToGroup,RemoveClientFromGroup) — assign roles and group memberships to a client - octo-cli — GetApiScopes and related scope commands (
CreateApiScope,UpdateApiScope,DeleteApiScope) — manage API scopes from the CLI - octo-cli — ApplyClientOverlay — append URIs to a blueprint-managed client without losing them on the next blueprint re-apply
- octo-cli — CleanClientOverlays — strip
overlay:*URI entries from every client (or just one overlay by name) — destructive cleanup before sanitised tenant dumps - Apply-IdentityOverlay — PowerShell cmdlet that drives the overlay endpoint from a declarative YAML file
- Studio OAuth Clients — the Data Refinery Studio client form + the "Clean overlay URIs before export" backup option
- MCP tools
apply_client_overlay/clean_client_overlays— the same two overlay operations exposed to AI assistants via the OctoMesh MCP server