Skip to content

The v1 docs follow nauthera-server's main branch and its 0.x releases on the way to 1.0, so fields and APIs can still change.

v1

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 under keys.* 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.grpc.tls.allowed_clients 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.svg

branding 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 orgOrg acme
Issuerhttps://auth.example.comhttps://auth.example.com/orgs/acme
Discovery/.well-known/openid-configuration/orgs/acme/.well-known/openid-configuration
JWKS/.well-known/jwks.json/orgs/acme/.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, /.well-known/openid-configuration/orgs/acme and /.well-known/oauth-authorization-server/orgs/acme.

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_verification_required, 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 /v1/admin/orgs/{slug}, 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: true

The 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.registration.enabled, off by default. With email_verification_required (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 IDTypeUse
{slug}-admin-consolePublic, PKCE required, authorization_codeAn 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-m2mConfidential, client_credentialsAutomation. 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.admin.bootstrap_secret_path (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.

Email

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-smtp

The 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_mode applies 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_mode hashes 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 with GOFIPS140=v1.0.0; an ordinary build run with GODEBUG=fips140=on does not count. On any other process the org fails closed: creating it fails with 409 over the admin API, or FAILED_PRECONDITION over gRPC, and an org that already exists is not served.

Deleting an org

DELETE /v1/admin/orgs/{slug}, 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.