Development users and tenants
Why this exists
Section titled “Why this exists”GET /.cratis/tenants and GET /.cratis/users are development-only discovery
endpoints. They have nothing to do with tenant resolution
at request time — they exist purely so a tool can ask your running application
“which tenants and users do you know about right now?”, sourced from wherever
your application already keeps that list, instead of a hand-maintained copy
pasted into the tool.
The primary consumer is Lens, the Cratis browser extension for exercising a running Arc application during development. Lens reads these two endpoints to populate its tenant and user pickers, then injects the corresponding tenant and identity headers into every request the inspected page makes while a selection is active — no restart, no hand-crafted headers. See Lens: where the tenant and user roster comes from for the extension side of this contract, with screenshots against seeded demo data. Arc’s own authentication layer treats both endpoints as anonymous — see Know which built-in routes are anonymous — including in production. A Spring Security chain, such as the default one the platform identity bridge adds, can still require authentication for them. Do not rely on that: scope development-only providers out of production builds or restrict the paths at trusted ingress.
Without any provider registered, both endpoints return an empty JSON array rather than failing.
Implement a tenants provider
Section titled “Implement a tenants provider”Register a TenantsProvider bean. It is a Kotlin fun interface, so a lambda
is enough:
import io.cratis.arc.tenancy.Tenantimport io.cratis.arc.tenancy.TenantIdimport io.cratis.arc.tenancy.TenantNameimport io.cratis.arc.tenancy.TenantsProviderimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass DevelopmentTenants { @Bean fun developmentTenantsProvider() = TenantsProvider { listOf( Tenant(TenantId.of("acme-corp"), TenantName("ACME Corporation")), Tenant(TenantId.of("widget-inc"), TenantName("Widget Inc")) ) }}provide() is a suspend function, so it can call a repository or a remote
service directly. GET /.cratis/tenants returns each Tenant as
{"id": "...", "name": "..."}.
Java registers an AsyncTenantsProvider instead, returning a
CompletionStage<List<Tenant>>:
import io.cratis.arc.tenancy.AsyncTenantsProvider;import io.cratis.arc.tenancy.Tenant;import io.cratis.arc.tenancy.TenantId;import io.cratis.arc.tenancy.TenantName;import java.util.List;import java.util.concurrent.CompletableFuture;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;
@Configurationpublic class DevelopmentTenants { @Bean public AsyncTenantsProvider developmentTenantsProvider() { return () -> CompletableFuture.completedFuture(List.of( new Tenant(TenantId.of("acme-corp"), new TenantName("ACME Corporation")), new Tenant(TenantId.of("widget-inc"), new TenantName("Widget Inc")) )); }}Implement a users provider
Section titled “Implement a users provider”UsersProvider (Kotlin) and AsyncUsersProvider (Java) follow the same
shape and return User, which pairs a host-neutral ArcPrincipal with
optional application-specific details:
import io.cratis.arc.authorization.ArcPrincipalimport io.cratis.arc.identity.Userimport io.cratis.arc.identity.UsersProviderimport org.springframework.context.annotation.Beanimport org.springframework.context.annotation.Configuration
@Configurationclass DevelopmentUsers { @Bean fun developmentUsersProvider() = UsersProvider { listOf( User( ArcPrincipal( name = "alice@contoso.com", isAuthenticated = true, roles = setOf("admin", "developer"), authenticationScheme = "aad" ), mapOf("department" to "Engineering") ), User( ArcPrincipal( name = "bob@contoso.com", isAuthenticated = true, roles = setOf("tester"), authenticationScheme = "aad" ), mapOf("department" to "QA") ) ) }}GET /.cratis/users returns each entry as
{"principal": {"id": "...", ...}, "details": {...}}.
Multiple providers compose
Section titled “Multiple providers compose”Register more than one TenantsProvider or UsersProvider — Arc aggregates
the coroutine providers in @Order sequence, then the Java async providers in
@Order sequence, and keeps the first entry seen for each tenant or user
identifier. A coroutine provider therefore wins over a Java async provider for the
same identifier, whatever their @Order values, so one source
can supply fixtures while another reads from a database or an external
service without either implementation knowing about the other.
See also
Section titled “See also”- Lens — the browser extension this page’s providers feed, with screenshots against seeded demo data.
- C#: implementing a tenants provider — the same discovery contract on ASP.NET Core.
- Tenancy — request-time tenant resolution, which this page’s providers are deliberately separate from.
- Authenticate and authorize Arc endpoints — the anonymous-route list these endpoints belong to, and
GET /.cratis/me.