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)

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

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.

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