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

Identity Broker

Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated

Every organization is its own issuer. An application whose users belong to several organizations would otherwise register a client at each one and work out which issuer a user belongs to. A broker is an organization whose clients sign in the users of other organizations, its members: the application connects once, as a plain OpenID Connect client of the broker, and the user is sent to their own organization from nothing but an email address.

  • The member signs its own users in. Its sign-in page, password policy, second factor, lockout, branding and audit log apply unchanged. The broker never sees a password.
  • The broker issues the tokens. The application trusts only the broker's issuer. The ID token names the member in member_org, its stable ID, and member_org_slug, so the application can tell organizations apart; sub is the broker's own account linked to the member's user.
  • Both organizations consent. A membership is active only once the broker has invited the member and the member has accepted. Either side can end it.
  • One hop. A broker cannot be a member, and a member cannot broker.

The pieces

PieceWhereWho declares it
The brokerany organization with an active membershipnothing to declare
The broker's invitation, and which of its clients it admitsa BrokerMembership with spec.memberthe broker's identity team
The member's acceptancea BrokerMembership with spec.brokerthe member's identity team
The email domains routed to a memberthe member's Organization spec.emailDomains, proved by a DNS TXT recordthe member's platform team, and whoever runs its DNS

The same can be done through the admin API (/v1/admin/orgs/{slug}/broker-memberships, /v1/admin/orgs/{slug}/email-domains). A membership side or a domain claim declared from Kubernetes cannot be changed there.

The membership

Each side is declared from a namespace of its own organization, so a team that cannot write in the other organization's namespaces cannot consent for it. The server checks each side against the operator's grant for that side's organization too.

spec.clients on the broker's side lists the broker's clients the membership admits. A client it does not admit never routes to that member: a broker can admit its members to Grafana and keep its Argo CD for its own users.

Routing by email domain

The broker sends an address to the member that has verified its domain, and to no one else. A member claims a domain in spec.emailDomains; the claim reports a TXT record to publish, and is verified once the record is found.

kubectl get organization acme -o jsonpath='{.status.emailDomains}'
[{"domain":"acme.example","state":"Pending",
  "record":{"name":"_nauthera-challenge.acme.example","type":"TXT",
            "value":"nauthera-domain-verification=…"}}]
  • A domain is verified for one organization at most. A claim on a domain another organization verified is reported Refused.
  • A domain verified by an organization that is not a member of this broker is answered as an unknown one, so the broker never reveals who owns a domain outside its members.
  • The record can live in Git: with external-dns reading DNSEndpoint resources (its crd source, and TXT among its managed record types), a DNSEndpoint with the claim's token publishes it. A DNS resolver may keep a "not found" answer from before the record existed for up to the zone's negative-caching time.
apiVersion: externaldns.k8s.io/v1alpha1
kind: DNSEndpoint
metadata:
  name: acme-example-email-domain
  namespace: acme
spec:
  endpoints:
    - dnsName: _nauthera-challenge.acme.example
      recordType: TXT
      recordTTL: 300
      targets: ["nauthera-domain-verification=…"]

A sign-in through the broker

The screens are on Broker sign-in.

Mapping members in the application

The application reads member_org_slug from the ID token or userinfo. Grafana, for example, maps each member to a Grafana organization of its own with one provider:

[auth.generic_oauth]
client_id = grafana
org_attribute_path = member_org_slug && [member_org_slug, email] || ['nauthera', email]
org_mapping = nauthera:1:Viewer acme:2:Viewer globex:3:Viewer admin@acme.example:2:Admin

The broker's own users carry no member_org_slug, and the expression gives them nauthera instead.

Limits today

  • Only members are routed. An address is sent to a member that verified its domain. An address of the broker's own organization, or at a domain no member verified, ends at "No organization found"; there is no fallback to the broker's own sign-in. To sign the broker's own staff in through it, give them an organization of their own that is a member, as for any other.
  • Domains decide. Choosing among several organizations for one address, after a one-time code proves control of it, is planned, not built.