Tenant Service
The Tenant Service is the multi-tenancy orchestrator for the Identity Platform, providing authorization-aware tenant management.
Getting Started
To start the development environment, run:
make dev
This command will:
- Start the necessary dependencies (PostgreSQL, Kratos, Hydra, OpenFGA) using Docker Compose.
- Build and run the service locally.
- Start an OIDC client on
http://localhost:4446 to facilitate login flows.
Kubernetes Development
To run the full stack in a MicroK8s cluster using Skaffold:
make dev-k8s
This command will:
- Build the Rockcraft image locally.
- Push the image to your configured registry (default
localhost:32000).
- Deploy dependencies (Postgres, OpenFGA) and the service to Kubernetes.
- Run the setup jobs (DB migrations, OpenFGA config).
- Port-forward the service and dependencies to localhost.
To clean up the Kubernetes resources:
make clean-k8s
Configuration
The service is configured using environment variables.
| Variable |
Description |
Default |
Required |
OTEL_GRPC_ENDPOINT |
OpenTelemetry gRPC Collector Endpoint |
|
No |
OTEL_HTTP_ENDPOINT |
OpenTelemetry HTTP Collector Endpoint |
|
No |
TRACING_ENABLED |
Enable OpenTelemetry Tracing |
true |
No |
KRATOS_ADMIN_URL |
Ory Kratos Admin API URL |
|
Yes |
INVITATION_LIFETIME |
Duration an invitation remains valid |
24h |
No |
LOG_LEVEL |
Logging Level |
error |
No |
DEBUG |
Enable Debug Mode |
false |
No |
PORT |
HTTP Server Port |
8080 |
No |
GRPC_PORT |
gRPC Server Port |
50051 |
No |
WEBHOOKS_API_TOKEN |
API token required to protect webhook endpoints |
|
No |
DSN |
PostgreSQL Connection String |
|
Yes |
DB_MAX_CONNS |
Maximum open DB connections |
25 |
No |
DB_MIN_CONNS |
Minimum open DB connections |
2 |
No |
DB_MAX_CONN_LIFETIME |
Maximum amount of time a connection may be reused |
1h |
No |
DB_MAX_CONN_IDLE_TIME |
Maximum amount of time a connection may be idle |
30m |
No |
AUTHORIZATION_ENABLED |
Enable OpenFGA authorization checks |
false |
No |
OPENFGA_API_SCHEME |
OpenFGA API Scheme (http/https) |
|
No |
OPENFGA_API_HOST |
OpenFGA API Host |
|
No |
OPENFGA_API_TOKEN |
OpenFGA API Token |
|
No |
OPENFGA_STORE_ID |
OpenFGA Store ID |
|
No |
OPENFGA_AUTHORIZATION_MODEL_ID |
OpenFGA Model ID |
|
No |
AUTHENTICATION_ENABLED |
Enable JWT Authentication |
true |
No |
AUTHENTICATION_ISSUER |
OIDC Issuer URL |
|
No |
AUTHENTICATION_JWKS_URL |
Manual JWKS URL (optional) |
|
No |
AUTHENTICATION_ALLOWED_SUBJECTS |
Comma-separated allowed subjects |
|
No |
AUTHENTICATION_REQUIRED_SCOPE |
Required scope claim |
|
No |
Authentication
The service supports JWT-based authentication using OIDC. By default, it is enabled.
Generating Tokens for Development
To generate a token for local development using the Client Credentials flow:
# Using explicit token URL
./app token --client-id <id> --client-secret <secret> --token-url <url>
# Using OIDC discovery (issuer URL)
./app token --client-id <id> --client-secret <secret> --issuer-url <url>
To use the CLI with authentication:
./app tenant list --token <jwt-token>
Workflows
The Tenant Service supports several key workflows for managing tenants and users, as defined in ID054.
1. Self-Service Registration
This flow ensures that every new user is automatically assigned a Tenant, eliminating "orphaned" identities.
How to run:
- Ensure the dev environment is running (
make dev).
- Visit
http://localhost:4446 in your browser.
- Click the link to start the login flow.
- You will be redirected to the Login UI. Click "Sign Up".
- Create a new account.
- Upon success, you will be redirected back to the callback URL, where you can inspect the ID Token. The token should contain a
tenant_id claim, indicating a tenant was auto-created.
2. User Invitation
Allows tenant owners to invite other users to their tenant.
How to run:
Use the CLI to simulate an invite. You need the Tenant ID from the previous step (or list them).
# List tenants to find your Tenant ID
./app tenant list
# Invite a user (email) to the tenant
./app tenant users invite <tenant-id> <email> <role>
# Example: ./app tenant users invite <uuid> bob@example.com member
3. Enterprise Onboarding
Manual provisioning flow for enterprise customers.
How to run (Admin CLI):
# 1. Create a new Tenant
./app tenant create "Acme Corp"
# Output: Tenant created: Acme Corp (ID: <uuid>)
# 2. Provision an Owner for the Tenant
./app tenant users provision <uuid> alice@acme.com owner
4. Tenant-Aware Login
Injects the tenant context into the login session.
How to run:
- Visit
http://localhost:4446 to start a new login flow.
- Log in with a user who belongs to multiple tenants (or created via Enterprise Onboarding).
- The UI should prompt you to select a tenant.
- After selection, the final ID Token issued will contain the specific
tenant_id you selected.
5. Tenant Switching
Allows users to switch between tenants they belong to.
How to run:
- In the "app" (simulated by
localhost:4446), click "Log Out" (or clear cookies).
- Start a new login flow (
http://localhost:4446).
- Log in again with the same user.
- Select a different tenant from the selection screen.
- The new token will reflect the switched tenant.
E2E Tests
The E2E tests are located in tests/e2e and designed to run in isolation with full authentication enabled.
The tests cover both HTTP/REST and gRPC interfaces:
- HTTP tests (
e2e_test.go): Test the REST API via the gRPC-gateway
- gRPC tests (
grpc_test.go): Test the native gRPC interface directly
To run the E2E tests:
cd tests/e2e
go mod tidy
go test -v .
This will:
- Spin up the full Docker Compose stack (Postgres, OpenFGA, Kratos, Hydra).
- Build the
tenant-service binary from the source.
- Create an OAuth2 client in Hydra for authentication.
- Run lifecycle tests with JWT authentication enabled.
Running Against Existing Deployment
If you already have a running deployment (e.g., via make dev), you can run tests without setting up the environment:
Option A: Using a JWT Token
E2E_USE_EXISTING_DEPLOYMENT=true \
HTTP_BASE_URL=http://localhost:8000 \
JWT_TOKEN=<your-jwt-token> \
make test-e2e
Option B: Using Client Credentials (exchanges for token automatically)
E2E_USE_EXISTING_DEPLOYMENT=true \
CLIENT_ID=<client-id> \
CLIENT_SECRET=<client-secret> \
make test-e2e