Skip to content

Workspaces, roles and environments

The five words the product is built from, which of them your tokens carry, and how a change to a role reaches a running application.

Five words carry the whole model, and they nest:

Account          — you, the customer, and the billing entity
└── App          — one authentication realm: your product
    └── Environment   — production and sandbox, exactly two, always
        ├── User      — a person with credentials, belonging to this environment
        └── Workspace — a group your customers manage, holding Members with roles

Nothing crosses an environment. A user of sandbox does not exist in production; a role defined in one is not the role in the other, even with the same name.

App

An App is one product with one set of users. It gets a slug, and the slug becomes two permanent addresses — https://acme.kleora.eu and https://acme.sandbox.kleora.eu. Those are the App's issuers: the iss claim of every token it mints, the host its sign-in pages are served from, and the base of its discovery document.

The slug cannot be changed afterwards. Every service that has ever verified one of your tokens has that string cached; picking it deserves the care a domain name gets.

Creating an App creates, in one transaction, both environments and in each of them: a default workspace, a spa client named Default, the signing keys, the default branding and settings, the reserved management permissions and the default email templates. There is nothing to provision afterwards.

Environment

Two per App, production and sandbox, and structurally identical. Same features, same endpoints, same shapes. They differ in the ways that keep them from being confused for one another:

ProductionSandbox
Issueracme.kleora.euacme.sandbox.kleora.eu
API key prefixkl_live_kl_test_
env claim in tokensproductionsandbox
Redirect URIshttps:// onlyalso http://localhost and http://127.0.0.1, any port, any path
Counted for billingyesnever

The SDKs refuse to mix them: a kl_test_ key with a production issuer, or a token whose env disagrees with the issuer it was configured with, fails at configuration time rather than in production at two in the morning. The environment is visible in every URL you paste, which is the cheapest guard there is against pointing production code at sandbox.

Each environment also holds its own settings — token lifetimes, session lifetimes, password rules, whether people may register themselves. The defaults are an access token of 15 minutes, a refresh token of 30 days, and a session that lasts 7 days with a 24-hour idle limit; each is adjustable within a range.

Promotion, rather than editing production twice

Configuration moves from sandbox to production in one operation: promote. It copies the settings, the branding, the permissions, the app-scoped roles and the email templates, and it can be run as a dry run first, which returns the same diff and changes nothing.

What it never copies is everything that belongs to real people: users, identities, sessions, workspaces, members, invitations, clients, API keys and signing keys. Production members keep the roles they hold; because roles are matched by slug, a member holding editor simply gains editor's new permission set on their next token.

Workspace

A workspace is a group your customers manage inside one environment — a company, a team, a project. A User joins one as a Member, and it is the membership that carries roles. The same person can be a member of several workspaces with different roles in each.

An environment is in one of two modes:

  • single — the default, and the right answer for a consumer product. There is one workspace, it is hidden, and every new user joins it automatically. Nobody is asked to choose anything at sign-in.
  • multi — workspaces are real. They appear in lists, they can be created, and a sign-in resolves to exactly one of them.

Switching to multi needs no migration. Switching back is allowed only when the default workspace is the only one left, because the alternative is deleting workspaces that still hold members on your behalf.

Which workspace a sign-in lands in

In multi mode, after the person has authenticated:

  1. If the authorization request pinned a workspace (the tenant parameter), that one — or the sign-in fails, rather than quietly falling back to another.
  2. Otherwise, if the session already has a workspace they are still a member of, that one is reused.
  3. Otherwise, exactly one candidate resolves to itself; several show a chooser.
  4. None at all does what the environment's settings say: refuse, or offer a "create your workspace" form.

The result is stored on the session, so every later token in that session carries the same workspace until it is deliberately switched.

Invitations

An invitation names an email address and the roles it will carry, and it is open for seven days. Accepting proves control of the address, so somebody invited who has no account signs up with the email pre-filled and read-only — and lands verified, because clicking the link was the proof. One open invitation per address per workspace; resending issues a new token and invalidates the old one.

Roles and permissions

A permission is a string of the form resource:verb — invoices:read, reports:write. You define your own; they mean whatever your application decides they mean.

A role is a bundle of them, and it has one of two scopes:

  • App-scoped — defined on the environment, assignable in every workspace of it. This is where admin and member usually live.
  • Workspace-scoped — defined inside one workspace, assignable only there. This is how one customer gives themselves a role the others do not have.

A role's slug is what tokens carry, so it is immutable after creation; the name and the description are not. Slugs cannot collide across the two scopes — an app-scoped admin blocks a workspace-scoped admin everywhere — which is what keeps a token's roles array unambiguous.

Kleora also creates a set of reserved management permissions in every environment (users:read, members:write, roles:write, and so on). You cannot delete or rename them, but you can put them in a role — and that is how you let a customer's own administrator manage their workspace's members through the API, with their own access token, without becoming an administrator of yours.

What the token carries, and when it changes

Every access token carries the resolved workspace and the authorisation that goes with it:

{
  "sub": "…",
  "tenant": "…",
  "tenant_slug": "acme",
  "roles": ["admin"],
  "permissions": ["invoices:read", "invoices:write"],
  "env": "production"
}

roles is the member's role slugs; permissions is the union of those roles' permissions, de-duplicated and sorted. Both are always present — [] rather than absent — so an authorisation check never has to test for absence first. Your API reads them from the token it has already verified.

The cost of that is the one thing to design around: a change to a role, or to a member's roles, is visible at the next token issuance — within the access token's lifetime for somebody holding one, immediately on their next refresh. There is no revocation list for access tokens. When a change has to take effect now rather than in fifteen minutes, revoke that user's sessions; the next request has no valid token at all.

Where to go next