Clients
An OAuth client belongs to exactly one organization. Today there are two ways to register one, with a third on the way.
| Path | Who uses it | Status |
|---|---|---|
gRPC AdminService.UpsertOIDCClient | Platform automation and nauthera-cli | Implemented |
Dynamic client registration (/register) | Applications holding an initial access token | Implemented |
OIDCClient resource reconciled by the operator | Kubernetes manifests | Planned (#377) |
Through the AdminService
UpsertOIDCClient is a declarative, idempotent full replace with an etag for
optimistic concurrency. When the spec asks for a secret (secret.generate), the server
generates one, stores only its argon2id hash, and returns the plaintext once, in the
response to the upsert that created it.
On a local stack, nauthera-cli drives the same RPC:
go run ./cmd/nauthera-cli create client my-app \
--redirect-uri https://app.example.com/callback \
--post-logout-redirect-uri https://app.example.com/ \
--scopes openid,profile,email,offline_access \
--grant-types authorization_code,refresh_token \
--pkce required_s256| Flag | Effect |
|---|---|
--public | Public client: no secret, PKCE required |
--jwks-uri | private_key_jwt client, verified against the keys at this https URL |
--org / --issuer | Home the client in another organization |
--frontchannel-logout-uri | Register a front-channel logout URI |
nauthera-cli talks to the AdminService over plaintext gRPC, so the server must have
server.grpc.allow_insecure: true. It is a development tool, not a way to manage
production.
Dynamic client registration
POST /register implements RFC 7591. Each registration must present an initial
access token: a bearer token for the target organization that carries the
nauthera.clients:register scope. The response includes a registration_access_token
and a registration_client_uri for RFC 7592 management. Store the token from the
registration response: read and update responses do not return it again (#78).
| Method | Path | |
|---|---|---|
POST | /register | Register a client |
GET | /register/{client_id} | Read its configuration |
PUT | /register/{client_id} | Replace its configuration |
DELETE | /register/{client_id} | Deregister it |
curl -s https://auth.example.com/register \
-H "Authorization: Bearer $INITIAL_ACCESS_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"client_name": "My App",
"redirect_uris": ["https://app.example.com/callback"],
"grant_types": ["authorization_code"],
"response_types": ["code"],
"token_endpoint_auth_method": "client_secret_basic",
"scope": "openid profile email"
}'The registration policy is fixed in code and cannot be loosened by configuration:
- Initial access token: always required. It is what binds the new client to an organization.
- Scopes: only
openid,profileandemail.offline_accessis deliberately excluded, so a self-registered client gets no refresh tokens and has to come back through/authorizewith a live session. Anynauthera.*scope is refused outright. - Grant types:
authorization_codeandrefresh_token. - PKCE:
required_s256by default. - Redirect URIs: at most 8.
Back-channel logout URIs (backchannel_logout_uri) can be registered only this way.
A client's display name is checked for characters built to be misread, and suspicious
names are recorded in the audit log.
Client settings
| Setting | Values |
|---|---|
| Token endpoint authentication | none, client_secret_basic, client_secret_post, private_key_jwt (ES256 or RS256 assertions). tls_client_auth and self_signed_tls_client_auth are refused. |
| Grant types | authorization_code, refresh_token, client_credentials, urn:ietf:params:oauth:grant-type:device_code. implicit and password are refused. |
| Response types | code |
| PKCE policy | required_s256 (default), optional, disabled. Only S256 is accepted. FAPI organizations force required_s256. |
| ID token signing | ES256 or RS256 (id_token_signed_response_alg). |
| Lifetimes | Per-client access, refresh and ID token TTLs override the issuer.*_ttl defaults. |
| Refresh tokens | Issued when the client is allowed to refresh and requests offline_access. They rotate on every use; reusing an old one revokes the whole family. |
Redirect URIs
Redirect URIs are matched exactly, with no wildcards and no prefix matching. Two relaxations that native apps need are not supported yet: any port on a loopback redirect (#84) and private-use URI schemes (#87).
Secrets
Rotating a secret currently means deleting the client and creating it again. Rotation in place is tracked in #351.
Scopes
Besides openid, profile, email and offline_access, the server reserves the
nauthera. prefix for its own resource scopes. These can be granted only to clients
registered through the gRPC AdminService. Dynamic registration refuses them.
| Scope | Grants |
|---|---|
nauthera.orgs:read, nauthera.orgs:write | The organizations admin API |
nauthera.users:read, nauthera.users:write | The users admin API |
nauthera.audit:read | The audit log API |
nauthera.clients:register | Dynamic client registration (an initial access token) |
Admin scopes also need a matching RBAC role. See Admin API & Users.