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.
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, ormoveover the API yet. Only create, get, and list are exposed today, for both Organizations and grants.