Organizations
Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated
An organization (org) is a tenant. Its users, sessions, OAuth clients, signing keys and audit events belong to it, and it is addressed by a slug.
- One issuer per org. The main org is served at the root of the server. Every other
org is served under
/orgs/{slug}. Each has its own issuer URL, discovery document and JWKS. - Isolated identities. An email address is unique within an org, not across the server. Sessions and single sign-on never cross orgs.
- Users sign in at their org's address. Nothing routes a user who types only an email address to the right org. Letting an org claim and verify its email domains, and sending an email-first sign-in to the org that owns the domain, is planned.
- Per-org keys. Every org other than the main one gets its own ES256 and RS256
signing keys when it is created. They are stored in the database, encrypted under the
deployment's key-encryption key (
keys.encryption_key). The main org signs with the keys configured underkeys.*and never uses the key-encryption key. See Keys & Secrets. - Per-org policy. Each org has its own FAPI mode, verified-address requirement, second-factor policy, network zones and default branding, and can be created in FIPS mode (see below).
The main org
The main org has the fixed slug default (orgs.default_slug) and cannot be deleted.
It governs the rest. Through the admin API, listing orgs needs the orgs.read permission
and creating, changing and deleting them needs orgs.write, each held through a
platform role in the main org; only platform-owner holds orgs.write. The gRPC
AdminService can also create, read, list, change and delete orgs. It does not check
roles: a caller needs the organizations operation and the org's slug in its
server. entry (see
the control plane). The main org is served
at the root only, so /orgs/default/… answers 404 unknown_org.
The main org is configured with the deployment, under orgs.main. Each setting given is
applied at every startup, and the admin API refuses to change it; a setting left out
stays editable. With the Helm chart:
config:
orgs:
main:
display_name: Acme Platform
fapi_mode: true
email_verification_required: true
branding: |
productName: Acme
logoUrl: https://acme.example/logo.svgbranding is text (YAML or JSON) because the configuration loader lowercases the keys of
a nested object. The main org's FAPI mode is read only at startup, so this is the way to
change it. No resource and no control-plane call can create, change or delete the main
org. See Configuration.
URLs
For an issuer https://auth.example.com and an org acme:
| Main org | Org acme | |
|---|---|---|
| Issuer | https://auth.example.com | https: |
| Discovery | / | / |
| JWKS | /.well-known/jwks.json | / |
| Authorize | /authorize | /orgs/acme/authorize |
| Token | /token | /orgs/acme/token |
Org discovery is also served at the RFC 8414 path-inserted form,
/ and
/.
Creating an org
Through the admin API, as a main-org caller holding
orgs.write (the platform-owner role):
curl -s https://auth.example.com/v1/admin/orgs \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"slug": "acme", "display_name": "Acme Corp"}'The slug must match ^[a-z0-9][a-z0-9-]*$ and be at most 64 characters. The body can
also set fips_mode, fapi_mode, email_, branding,
second_factor and network_zones. The response contains the org's issuer and,
only this once, admin_m2m_secret, which is the secret of the org's admin machine
client.
The CLI (create org, get org, update org, delete org) calls the gRPC
AdminService. create org takes --display-name, --fapi-mode,
--require-email-verification, --fips-mode, --second-factor-file and --zone; see
nauthera-cli.
go run ./cmd/nauthera-cli create org acme --display-name "Acme Corp"An org created this way gets no admin_m2m_secret. The gRPC CreateOrganization call
returns none, so nobody holds the secret of the org's {slug}-admin-m2m client and that
client cannot be used.
After creation, PATCH /, the gRPC UpdateOrganization call and
nauthera-cli update org change the display name, FAPI mode, verified-address
requirement, second-factor policy and network zones, and the first two also the default
branding. The slug and FIPS
mode are fixed at creation. A change reaches every replica within 30 seconds.
Declaring an org as a manifest
With the operator, an org can be declared as a cluster-scoped
Organization resource:
apiVersion: nauthera.io/v1alpha1
kind: Organization
metadata:
name: acme
spec:
slug: acme
displayName: Acme Inc.
namespaces: [acme] # the namespaces whose resources configure acme
emailVerificationRequired: trueThe org's clients, identity providers, directories, brandings and broker memberships are
declared in the namespaces it lists in namespaces; see
Which organization a resource configures.
The operator creates the org, or adopts the one that already holds the slug, and writes
the settings in which it differs. Its identity needs the organizations operation and
the slug in the server's allowed_clients, which the Helm chart's default does not
grant. Deleting the resource leaves the org serving unless it was created with
deletionPolicy: Delete. An org created this way gets no admin_m2m_secret, as with
the CLI.
Brokering other orgs
An org can sign in the users of other orgs through its own clients, as an identity broker: each member org accepts the broker, and the broker routes an email address to the member that verified its domain.
Identity providers and sign-up
An org can let its users sign in and sign up through external OpenID Connect providers,
such as Google or Entra ID, or through GitHub. Each provider belongs to one org and is
registered with an IdentityProvider resource
or nauthera-cli create identity-provider. A provider is offered only to the clients that
list it, each saying whether it allows sign-in, sign-up or both. The
admin dashboard lists an org's providers. See
Social sign-in. An org's LDAP directory or
Active Directory works the same way: an
LDAPDirectory lets its users sign in, or sign up,
with their directory password on the clients that list it.
Self-service sign-up, with a password or through a provider, is a deployment-wide
setting, auth., off by default. With
email_ (emailVerificationRequired on the resource), an account
whose address is not verified cannot finish signing in until it follows an emailed link,
which needs the org to have an email relay. See
Sign up.
Second-factor policy and network zones
Each org has a second-factor policy that narrows the deployment's mfa policy and that
each of its clients can narrow further. It says which methods (totp, passkey) users
may use, whether a factor is required, whether a passkey alone signs a user in, how long
a browser that passed a factor is spared it, and rules that narrow it for sign-ins
outside or inside named network zones, from a new network or from a new browser. A
policy that states more than the deployment allows is refused. The org's
admin dashboard runs under the org's policy and is never
spared the challenge.
An org can name its own network zones (address ranges) beside the deployment's
security.network_zones, for its rules and its clients' rules to use. It may not reuse
a deployment zone's name, and only deployment zones can spare a challenge. See
Multi-Factor Authentication.
Admin clients
Every org gets two reserved OAuth clients so that people and automation can reach its admin API:
| Client ID | Type | Use |
|---|---|---|
{slug}-admin-console | Public, PKCE required, authorization_code | An administrator signs in interactively, as the admin dashboard at {issuer}/admin/ does. The admin API reads that user's roles in the org from the database on every call. An org's own users can hold its org roles (org-viewer to org-owner); platform roles count only in the main org. |
{slug}-admin-m2m | Confidential, client_credentials | Automation. Its org and admin roles are fixed when it is provisioned. |
For the main org, the M2M secret is written once, when the client is first created,
to auth. (mode 0600, never overwritten), and never to a
log. That path is empty by default, and then the secret is discarded and cannot be
recovered or rotated. Set it before the server's first start if you want this
credential; read the file, store the secret, then delete the file.
Email addresses listed in auth.admin.subjects are granted platform-owner in the main
org at startup. Each address is granted once, to the main-org account that holds it on the
first start that finds one. Removing an address later never demotes anyone. See
Admin API & Users.
An org sends email for new-sign-in alerts,
password reset links, email verification links, invitations,
emailed sign-in codes, notices of a changed password,
second factor or recovery codes, role grants and revocations, and a suspended, blocked or
reactivated account, in the org's branding and the recipient's language. It sends through
its own SMTP relay when it has one, and otherwise through the deployment's
(mail.smtp.*). An org with neither sends no email, and those features stay off for it.
Per-org relays are configured with the deployment, under mail.organizations, by slug.
With the Helm chart, list them in mailOrganizations; each entry names an existing
Secret with the relay's credentials, which the chart mounts as files:
mailOrganizations:
- slug: acme
smtp:
host: smtp.acme.example
port: 587
tls: starttls
from:
address: no-reply@acme.example
name: Acme
credentials:
existingSecret: acme-smtpThe admin dashboard's Email page shows which relay an org uses and sends a test message. Email describes relays, TLS and the outbox in full.
FAPI mode and FIPS mode
fapi_mode can be set when an org is created and changed afterwards; fips_mode is
fixed at creation:
fapi_modeapplies part of the FAPI 2.0 Security Profile to the org. It requires pushed authorization requests and PKCE with S256, signs only with ES256, and caps the authorization code lifetime at 60 seconds. It does not refuse shared-secret clients or require sender-constrained tokens.fips_modehashes passwords and recovery codes with PBKDF2-HMAC-SHA-256 instead of argon2id. It needs the Go FIPS 140-3 module on and pinned to a frozen version, as in a binary built withGOFIPS140=v1.0.0; an ordinary build run withGODEBUG=fips140=ondoes not count. On any other process the org fails closed: creating it fails with409over the admin API, orFAILED_PRECONDITIONover gRPC, and an org that already exists is not served.
Deleting an org
DELETE /, the gRPC DeleteOrganization call, or deleting an
Organization resource under deletionPolicy: Delete removes an org other than the main
one. The replica that deleted it stops serving it at once, and the others within 30
seconds. Only the org's own record is deleted, with its second-factor policy, network
zones and queued email: its users, clients and sessions stay in the database. The
reserved admin clients left behind also hold the slug: creating an org with the same slug
again is refused with 409 (FAILED_PRECONDITION with the reason
RESERVED_CLIENT_HELD over gRPC), naming the leftover client to remove first.