Operator or dynamic registration?
Docs version v1 (main)Nauthera is pre-1.0, in a design-partner programUpdated
nauthera-server implements dynamic client registration at /register
(Clients), so an application can register
its own OAuth client at runtime. That raises a fair question: if an app can register
itself, why run an operator and write
OIDCClient manifests at all?
Because dynamic registration answers a different question. It lets software register with an authorization server it has no prior relationship with. For the applications you build and deploy yourself, you already have that relationship: you own the app, the cluster and the identity provider. What those apps need is review, a lifecycle and secret delivery, and dynamic registration leaves all three to each app.
What dynamic registration is for
RFC 7591 starts from the observation that
"traditionally, registration of a client with an authorization server is performed
manually" (§1), and defines how a client can instead register itself by POSTing its
metadata to a registration endpoint. The RFC names the case it exists for: client
developers "will need to use dynamic registration when they are unable to predict aspects
of the software" (§1.2), such as which authorization servers it will meet, or how many
instances of it will run.
The cases that fit:
- Software deployed by other people. A product your customers install, each against their own identity provider, can't ship with a client ID baked in.
- Clients meeting authorization servers they don't know in advance. Open ecosystems, where a client discovers a server and registers on the spot.
- One registration per installed instance, such as a native app on each device.
Two of its tools address the trust problem this creates:
- An initial access token limits who may register: it "is required when the authorization server limits the parties that can register a client" (§1.2). Without one, registration is open: anyone can register (Appendix A.1.1).
- A software statement is a signed JWT in which a publisher vouches for the client's metadata, which then "MUST take precedence" over what the client sends (§5).
RFC 7592 adds reading, updating and deleting a registration with a registration access token. It is an Experimental RFC, not a standard, and "not all authorization servers supporting dynamic client registration will support these management methods" (Abstract).
Why it isn't the better way for your own apps
Every value is the client's word
Without a software statement, the authorization server "MUST treat all client metadata as self-asserted" (RFC 7591 §5). A rogue client "might use the name and logo of a legitimate client that it is trying to impersonate", and a server allowing open registration "must be extremely careful with any URL provided by the client" (§5).
nauthera-server takes that at its word. It accepts registrations only with an initial access token for the target organization, and holds every self-registered client to a fixed policy that configuration cannot loosen:
- scopes
openid,profileandemailonly, and nooffline_access, so no refresh tokens; - PKCE always
required_s256; - the issuer's default token lifetimes;
- no per-client identity providers or second-factor policy, which are set through the control plane only.
See Clients for the full policy. A
client declared in an OIDCClient manifest has gone through your review, so it can have
all of these.
The app has to keep its own credentials
A registration answers with a client_secret and, for RFC 7592, a
registration_, a bearer token that "MUST be protected by the developer or
client" (RFC 7592 §5). nauthera-server returns that token only in the registration
response, never again on a read or an update. So each app needs somewhere durable to keep
two secrets it created at runtime, and a way to get the initial access token in the first
place, which is itself a secret someone has to hand it.
With the operator, the server mints the secret once and keeps only a hash, and the
operator writes the secret into a Secret in the app's namespace. The app only reads it.
Nobody holds a token for managing the registration.
Replicas and restarts multiply clients
RFC 7591 allows either a client ID per instance or one shared by all instances (Appendix A.4), but not shared credentials: a server "SHOULD NOT issue the same client secret to multiple instances of a registered client, even if they are issued the same client identifier" (§5), and the registration access token "should not be shared between instances of a client" (RFC 7592, Appendix A). nauthera-server mints a new client ID for every registration, so a Deployment with three replicas that each register on start-up creates three clients, and more on every restart that lost its state. If they instead share one registration, someone has to coordinate who registers and where the result is stored, which is the job an operator does.
An OIDCClient is one client, whatever the replica count; every pod mounts the same
Secret.
Nobody cleans up after a crash
Under RFC 7592, a client "makes an HTTP DELETE request" to deprovision itself (§2.3). A pod that is killed, evicted or scaled away never does, so its client stays registered with a valid secret.
With the operator, deleting the manifest triggers spec.deletionPolicy: Retain (the
default) keeps the client and releases its Secret, and Delete removes the client. A
periodic sweep also removes Delete-policy clients whose manifest disappeared without the
finalizer running. See Deleting a manifest.
No review, no status, no drift
A dynamically registered client exists because some process holding a token decided it should, and RFC 7592 lets whoever holds the registration access token change it later. Nothing in your repository records that the client exists or what it should look like.
An OIDCClient is a manifest in Git, reviewed like the Deployment beside it. Its status
reports Ready, Refused, Unreachable or Drifted, and a change made outside the
operator is reported, not silently kept or overwritten. See
Changes made outside the operator.
Side by side
| Dynamic registration (RFC 7591/7592) | OIDCClient and the operator | |
|---|---|---|
| Who decides a client exists | The app, at runtime, with an initial access token | A manifest, reviewed and merged |
| Trust in its metadata | Self-asserted; nauthera-server applies a fixed, narrow policy | Reviewed; the full client settings are available |
| Refresh tokens | No (offline_access is refused) | Yes, with allowRefresh |
| Where the secret lives | Wherever the app keeps it | A Secret in the app's namespace; the server keeps a hash |
| Managing the registration | A registration access token the app must protect | Kubernetes RBAC on the resource |
| Replicas | One registration per instance, or coordination you build | One client, one Secret for every pod |
| Removal | The app sends DELETE, if it gets the chance | deletionPolicy, plus the orphan sweep |
| Visibility | Nothing outside the server | status, events, and drift on the resource |
When dynamic registration is the right tool
Use /register when the client is not yours to declare:
- Third-party or customer-deployed software that should register against your server. Give it an initial access token for one organization, and the policy above bounds what it can do.
- Software that meets many authorization servers, and can't be configured for each in advance.
- A client that needs a back-channel logout URI.
backchannel_logout_urican be registered only through/registertoday.
Outside Kubernetes, your own automation can also register clients through the gRPC
AdminService, as nauthera-cli does, with the same settings as the operator.
Why Nauthera ships both
Dynamic registration is a standard, so clients that expect it work with nauthera-server (see the standards page for what is supported). The operator is the path for your own applications on Kubernetes: their clients are declared, reviewed and delivered like the rest of the app, and none of the steps above is left for each app to get right.