Authentication
OctoMesh uses OAuth 2.0 and OpenID Connect (OIDC) for authentication. This page explains how authentication works, which flows are supported, and what happens during the login process.
Overview
Supported Flows
Authorization Code with PKCE
Used by web applications like Data Refinery Studio. The browser redirects to the Identity Service login page, the user authenticates, and the browser receives an authorization code that is exchanged for tokens.
- PKCE (Proof Key for Code Exchange) is required — prevents authorization code interception
- No client secret needed for public clients (SPAs)
- Supports
offline_accessfor refresh tokens
Device Code
Used by octo-cli and other devices without a browser. The CLI displays a URL and code, the user opens the URL in any browser, enters the code, and authenticates.
- The CLI polls
/connect/tokenuntil the user completes authentication - Supports
offline_accessfor long-lived sessions
Client Credentials
Used for service-to-service communication where no user is involved. The client authenticates with its own credentials (client ID + secret) and receives an access token.
- No user context — the token has no
subclaim - Client-credentials tokens bypass tenant authorization (no
allowed_tenantscheck) - Used for background jobs, inter-service calls, and automated processes
Multi-Tenant Authentication
Authentication in OctoMesh is always scoped to a tenant. The tenant ID determines which user database, identity providers, and roles are used.
Tenant Resolution
Different endpoints resolve the tenant differently:
| Endpoint | Tenant Source |
|---|---|
REST API (/{tenantId}/v1/...) | URL path segment |
/connect/authorize | acr_values=tenant:{tenantId} query parameter |
/connect/token | Mapped from authorization code or refresh token |
/connect/endsession | Decoded from id_token_hint JWT |
Per-Tenant Cookie Isolation
The Identity Service scopes authentication cookies per tenant by appending the tenant ID to cookie names:
| Standard Cookie | Tenant-Scoped Cookie |
|---|---|
.AspNetCore.Identity.Application | .AspNetCore.Identity.Application.{tenantId} |
idsrv | idsrv.{tenantId} |
idsrv.session | idsrv.session.{tenantId} |
This prevents session leakage between tenants when a user has sessions in multiple tenants on the same browser.
Per-Tenant Identity Provider Schemes
External identity provider schemes are registered with a tenant prefix to prevent conflicts:
{tenantId}:{providerName}
Examples:
octosystem:Google
customer-project:AzureAD
Each tenant independently configures which providers are available and their credentials. Providers are dynamically registered at runtime — adding or updating a provider takes effect immediately without restarting the service.
Access Token Structure
When authentication succeeds, the Identity Service issues a JWT access token containing:
Standard Claims
| Claim | Description | Example |
|---|---|---|
sub | User ID | "a1b2c3d4" |
preferred_username | Username | "john.doe" |
name | Display name | "John Doe" |
email | Email address | "john@example.com" |
given_name | First name | "John" |
family_name | Last name | "Doe" |
OctoMesh-Specific Claims
| Claim | Description | Example |
|---|---|---|
tenant_id | Tenant the user logged into | "customer-project" |
allowed_tenants | Tenants the user may access (repeated claim) | "customer-project", "octosystem" |
role | Effective roles (direct + group-inherited, repeated claim) | "Development", "DashboardViewer" |
home_tenant_id | For cross-tenant users: their parent tenant | "octosystem" |
Effective Roles
The role claims include the union of:
- Roles directly assigned to the user
- Roles inherited through group membership (resolved recursively up to 10 levels of nesting)
Roles are resolved at token issuance time and updated on token refresh.
Allowed Tenants
The allowed_tenants claims are resolved from:
- The login tenant — always included
- The home tenant — for cross-tenant users, their parent tenant
- Ancestor tenants — walks up the tenant hierarchy via OctoTenantIdentityProvider parent references (up to 10 levels)
- Descendant tenants — BFS traversal down through child tenants where an ExternalTenantUserMapping exists for this user (cascading through multiple levels)
Token Validation by Services
All OctoMesh services validate incoming requests using the same pattern:
Bearer Token Authentication
Services validate JWT tokens against the Identity Service's signing keys. The token must:
- Be a valid JWT signed by the Identity Service
- Not be expired
- Contain the required scopes for the requested operation
Tenant Authorization
After standard JWT validation, the TenantAuthorizationMiddleware enforces tenant access:
- Extract
{tenantId}from the request URL path - Read the
allowed_tenantsclaims from the validated token - If the route tenant is not in the allowed list → 403 Forbidden
- Client-credentials tokens (no
subclaim) bypass this check
Request: GET /customer-project/v1/runtime/entities
Token claims:
sub: "a1b2c3d4"
allowed_tenants: ["customer-project", "octosystem"]
→ "customer-project" is in allowed_tenants → Access granted
Request: GET /other-tenant/v1/runtime/entities
Token claims:
sub: "a1b2c3d4"
allowed_tenants: ["customer-project", "octosystem"]
→ "other-tenant" is NOT in allowed_tenants → 403 Forbidden
Authentication Flow: Step by Step
This section traces a complete authentication flow for a browser-based application.
1. User Opens Application
The client application (e.g., Data Refinery Studio) detects no valid session and redirects to the Identity Service:
GET /connect/authorize?
client_id=octo-data-refinery-studio
&response_type=code
&scope=openid profile email role octo_api
&redirect_uri=https://studio.example.com/callback
&code_challenge=...
&code_challenge_method=S256
&acr_values=tenant:customer-project
2. Identity Service Presents Login Page
The Identity Service renders the login page for the customer-project tenant, showing the available identity providers (e.g., "Corporate Azure AD", local login form).
3. User Authenticates
The user either:
- Enters username/password (validated against the tenant's local user database)
- Clicks an external provider button (redirected to Google, Azure AD, etc.)
- Is authenticated via a parent tenant (if an OctoTenantIdentityProvider is configured)
4. First Login Processing
On first login via an external provider, the Identity Service:
- Creates a local user record linked to the external identity
- Checks email domain group rules and adds the user to matching groups
- If the provider has a DefaultGroupRtId, adds the user to that group
- Resolves effective roles from group memberships
5. Token Issuance
The Identity Service:
- Resolves all effective roles (direct + group-inherited)
- Resolves allowed tenants (login tenant + home tenant + child tenants with mappings)
- Issues a JWT access token with all claims
- Returns the token to the client application
6. API Calls
The client includes the access token in API requests:
GET /customer-project/v1/runtime/entities
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
The target service validates the token and checks tenant authorization before processing the request.
Initial Setup
The very first time the Identity Service starts, no users exist. The setup endpoint creates the initial administrator:
octo-cli -c Setup -e "admin@example.com" -p "SecurePassword123"
This endpoint:
- Verifies no users exist in the system tenant (fails otherwise)
- Creates the admin user with the specified credentials
- The admin can then log in and configure tenants, clients, and providers
After setup, the system tenant has:
- The admin user
- Default clients (
octo-cli, Swagger UI) - Default identity resources (openid, profile, email, role)
- Default API scopes and resources
- Default identity providers (Google, Microsoft — disabled; system tenant only)
- Default roles and TenantOwners group