Skip to main content

Organizations

An Organization is a customer account inside your tenant — the way you model your own B2B customers, business units, or reseller-managed accounts. An Organization has its own users, its own roles, and (optionally) its own signing key, all scoped entirely within it. It is not a separate tenant of the O2ID deployment: it shares your tenant's OIDC issuer family and management surface, addressed at its own path underneath yours.

Organizations can nest: an enterprise customer can have its own child organizations (departments, subsidiaries, managed accounts), to whatever depth your product needs.

note

Managing Organizations requires a role with the organizations:create/ organizations:read scopes — see Using o2idctl for how to log in, and Managing Roles for how to create and assign roles.

Creating an Organization

o2idctl organizations create --slug acme --name "Acme, Inc."
o2idctl organizations get <id-or-slug>
o2idctl organizations list

These operate on whatever tenant your current o2idctl profile targets (see o2idctl use) — running them while targeting your own tenant creates a top-level Organization under it. get accepts either an Organization's ID or its slug: a slug alone is unambiguous, since it's unique across your entire Organization tree, not just among direct siblings (see Addressing below).

Addressing

An Organization is addressed at a flat path, regardless of how deeply it's actually nested:

GET /t/{your-tenant}/org/{organization}/.well-known/openid-configuration
GET /t/{your-tenant}/org/{organization}/oauth2/authorize

The path is always exactly /t/{tenant}/org/{slug} — two hops — no matter how deeply the Organization is actually nested, because an Organization's slug is unique across its whole tenant, not just among its direct siblings. Internally, creating an Organization under another Organization still records the real parent/child relationship (organizations list still only returns direct children of whatever path you addressed) — flattening is purely an addressing convenience, not a change to the hierarchy itself.

Reaching a child organization's own children

organizations list/get only return the direct children of whatever Organization you're currently targeting. Use organizations switch to target a different one for every subsequent command in your terminal session — reach any Organization directly, regardless of how deeply it's actually nested, since its slug is unique across the whole tenant:

o2idctl organizations create --slug emea --name EMEA
o2idctl organizations switch emea
o2idctl organizations create --slug germany --name Germany # a child of emea
o2idctl organizations list # emea's own children
o2idctl organizations switch # back to the tenant root

For a one-off command without changing what your session is switched into, set O2IDCTL_TENANT instead (tenant slug, then org, then the Organization's own slug):

O2IDCTL_TENANT=acme/org/emea o2idctl organizations list

Signing keys

An Organization inherits its parent's signing key by default — most never need their own, and O2ID resolves the inherited key transparently at every level, however deep the nesting goes. This is invisible day to day: ID tokens, JWKS, and discovery documents all behave exactly as they would for your tenant itself, just at the Organization's own path and issuer.

To give an Organization a dedicated signing key instead of inheriting one, pass --key-mode own at creation:

o2idctl organizations create --slug acme --name "Acme, Inc." --key-mode own

Delegated administration

A newly created Organization starts with no users of its own, and organizations create has no equivalent of tenants create's --admin-email/--admin-password seeding — an Organization always nests under something, so there's always an ancestor available to administer it directly instead.

An administrator holding the organizations:manage scope at a tenant or Organization can manage every resource inside every descendant Organization — however deeply nested — using their own existing bearer token, with no separate account or grant needed at the target:

o2idctl organizations create --slug acme --name "Acme, Inc."
o2idctl organizations switch acme
o2idctl users create --email admin@acme.example --password correct-horse-battery-staple
o2idctl organizations switch

organizations switch only changes which path subsequent commands address — it keeps using the same credentials from your last o2idctl login. Running users create against Acme's own path with those credentials is exactly what organizations:manage (or the wildcard *) authorizes. From here, create a role and assign it the same way you would for a fresh tenant (see Managing Users and Managing Roles), still switched into Acme.

organizations:manage bypasses the specific scope a request would otherwise need (the same way * does locally) but only reaches downward — never to a sibling Organization, and never up to the tenant holding it.

Shared applications

A newly created Organization has no OAuth applications of its own. Rather than registering a separate application in every Organization, share an existing one owned by your tenant (or a parent Organization) — its own members can then sign in with it directly. Sharing is governed by a policy, set with --policy:

o2idctl applications share <app-id> --policy all
o2idctl applications share <app-id> --policy selective --organizations acme,globex

--policy all shares with every current and future descendant Organization — a brand-new Organization created afterward is covered immediately, with nothing further to run. --policy selective shares with exactly the Organizations named by --organizations (a comma-separated list of IDs or slugs, each must be a descendant of whatever tenant/Organization you're currently targeting) and no others. --scope (repeatable) narrows the application's own scope ceiling further for use there — it can only narrow, never widen, and * is never accepted. Calling share again updates the existing share rather than duplicating it.

o2idctl applications unshare <app-id>
o2idctl applications unshare <app-id> --organizations acme,globex

unshare <app-id> with no --organizations revokes an active --policy all share. unshare <app-id> --organizations acme,globex revokes those specific Organizations' own --policy selective shares only — it has no effect on an Organization that's only reachable via an active --policy all share; unshare that first. Unsharing an Organization with no active share of its own is a no-op, not an error.

Once shared, the application is usable exactly like a native one at the Organization's own path — the same client_id works at /t/{tenant}/org/acme/oauth2/authorize, and a member signs in with their own Organization credentials (e.g. the admin created above). The resulting token is minted and stored at the Organization itself, not the owning tenant. An application's current sharing state — its policy, and (for selective) the Organizations it's shared with — is included directly in o2idctl applications get <app-id>'s response. Requires the applications:share-with-organizations scope (or applications:read to view it).

Grants

A grant authorizes a specific user from elsewhere in your tenant tree — a different Organization, or the tenant itself — to act at whatever node you're currently targeting, without creating them a separate account there:

o2idctl grants create --subject-user-id <user-id> --scope users:read --include-descendants
o2idctl grants list

--subject-user-id is the user's ID at their own home tenant/Organization — get it from o2idctl users list there. --scope bounds what they may do at this node (repeatable; a grant can never carry *, regardless of your own scopes). --include-descendants additionally extends the grant to every descendant of this node.

A grant is what lets that user's own access token — minted at their home node — be exchanged for a token scoped to this node instead, via the Organization switch grant. Requires the grants:create/grants:read scopes.

This is narrower than Delegated administration in two ways: it authorizes one specific user rather than everyone holding a scope, and it never cascades to descendants unless --include-descendants is set explicitly. Reach for a grant when you want to hand one user access to one (sub)tree; reach for organizations:manage when a tenant's own administrators should be able to manage every Organization beneath them as a matter of course.

Current status

  • No dedicated, lighter-weight self-service admin surface yet. Managing an Organization's users and roles today means using the same management API as your own tenant, with a bearer token scoped to that Organization (e.g. one belonging to a user created via Delegated administration, or via a grant — see Grants).
  • No custom domain support yet. Verifying an Organization's own domain for sign-in (e.g. home-realm discovery) is planned, not yet implemented — see Shared applications for the part of this that IS implemented: reusing an existing application across Organizations.
  • No update, delete, or move over the API yet. Only create, get, and list are exposed today, for both Organizations and grants.