Skip to main content

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_access scope
  • 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 sub claim
  • Authenticates with client ID + client secret
  • Bypasses tenant authorization middleware (no allowed_tenants check)
  • 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_access for 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 IDTypePurpose
octo-cliDevice CodeCLI tool for administration
octo-idenityServices-swaggerAuthorization Code (PKCE)Swagger UI for Identity API
octo-data-refinery-studioAuthorization Code (PKCE)Data Refinery Studio (auto-provisioned; RefineryStudioUrl defaults to https://localhost:4200)
octo-mcpServices-swaggerAuthorization Code (PKCE)Swagger UI for the MCP Services REST API
octo-mcpServices-deviceDevice Code + Token ExchangeMCP Services: device-flow login (authenticate tool), silent token refresh, and cross-tenant token exchange (switch_tenant)

Dynamically Registered Clients (octo-dcr-*)​

Interactive MCP clients such as Claude Code self-register via RFC 7591 Dynamic Client Registration (POST /connect/register) instead of using a pre-configured client. These clients:

  • are named octo-dcr-{random} and marked as dynamically registered
  • are hard-constrained at registration: loopback-only redirect URIs, PKCE, public client (no secret), server-fixed scopes — client-supplied scopes are ignored
  • expire automatically (default 90 days) and are swept by the token-cleanup job, including their per-tenant mirrors
  • can be listed like any other client (octo-cli identity-services client list) but should not be edited by hand

Registration is enabled by default and can be disabled per deployment with OCTO_IDENTITY__DYNAMICCLIENTREGISTRATION__ENABLED=false. See Authentication → Dynamic Client Registration.

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​

SourceOwned byLifecycleTypical use
baseBlueprint seedRewritten on every blueprint version bumpDefault URIs shipped with the service
apiREST API (Studio / octo-cli / direct HTTP)Survives every blueprint re-applyOperator-added URIs
overlay:<name>Overlay cmdletSurvives every blueprint re-applyPer-machine or per-team overlays
family:<name>Family reconciler reading env configEphemeral — fully regenerated on every restartMulti-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 (api source) 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:

NeedMechanismWhere to author it
URIs that exist in every deployment of every environmentBlueprint baseSystem.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 changeDB result on next restart
New member addedNew entry materialised with Source = "family:NAME"
Member removedEntry deleted from DB
Family fully unconfiguredAll family:NAME entries removed
No changeNo 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​

A CORS origin is scheme + host + port and never carries a trailing slash, while redirect URIs are matched exactly as registered. The reconciler therefore strips trailing slashes when writing to AllowedCorsOrigins only — RedirectUris and PostLogoutRedirectUris keep their trailing slashes. A single family member configured as https://localhost:5173/ 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.

info

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"

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:

ScopeAccess LevelDescription
octo_apiFull accessRead and write access to all OctoMesh APIs
octo_api.read_onlyRead onlyRead-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​

ResourceDisplay NameDescriptionScopes
octoAPIOcto APIUnified access to all Octo platform APIsocto_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:

ResourceClaimsPurpose
openidsubRequired for OIDC — user identifier
profilename, given_name, family_nameUser profile information
emailemail, email_verifiedUser email address
roleroleUser 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:

  1. 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"
  2. Grant required scopes:

    octo-cli -c AddScopeToClient -id "my-dashboard" -n "octo_api"
  3. 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