Skip to content

Security operations

An admin API operation needs qauth:admin:security because of what it does, not because of the screen it sits on (ADR-019, Decision 7). Each admin operation is defined once, in the registry in apps/auth-server/src/app/helpers/admin-operations.ts, with its class, the effects that make it a security operation and the permissions it needs. This page is generated from that registry.

No admin operation is registered yet, so no admin route can pass the boot check below: the rules are in place for the operations to come.

EffectAn operation with it
credential-pathChanges which credentials, links or addresses can authenticate or recover an account.
token-contentChanges what a token asserts or who receives it.
issue-for-otherIssues a token or session for someone other than the caller.
administrationChanges who administers.
importImports a realm file, or promotes a sandbox configuration, whose diff holds any other effect.
federation-operatorActs as a federation operator.
key-custodyRotates a key, rotates the KEK or retires a key.
realm-lifecycleDeletes a realm or transfers its ownership.
authority-tree-powerRevokes by agent or disables a transmitter.
posture-looseningLoosens the environment posture, lengthens a token or session lifetime or the key rotation interval, or shortens or switches off audit-event retention.
bulk-revocationRevokes sessions or refresh-token families in bulk.

The list is closed. A definition that names another value is refused, and a request whose classification names one counts as a security operation. The tags are published on each admin operation of the OpenAPI document as x-qauth-effects (API conventions).

  • An operation is of class security if and only if it has at least one effect. The registry refuses a read or write operation with an effect, and a security operation with none.
  • Its class is the strictest class among its permissions (read < write < security). So an operation with an effect needs a security permission, which only qauth:admin:security lets a token use: no role reaches it through qauth:admin:write.
  • An operation whose request decides what it needs, such as a policy patch or a realm-file import, declares the strictest case (class security, every effect it may have and every permission it may need) and a classify function. classify is pure: it reads copies of the request’s method, path parameters, query and body, and the operation’s current state, and returns the class, the effects and the permissions that request needs, within the declaration. A merge patch’s field that classify has no rule for fails closed only when classify takes the changed fields from the body, with mergePatchLeafPaths(body) (which walks the body’s own keys: an array is one field, a null member is a change), and classifies them with classifyByField, which throws on a field its table does not hold. A request-dependent merge patch operation uses that pattern; a classify that reads only the fields it expects never sees the others.
  • By effect, a policy change that lengthens an access-token, refresh-token or session lifetime or the key rotation interval, or shortens or switches off audit-event retention, is posture-loosening, a security change. Shortening a lifetime or lengthening retention is a write.
  • An operation that reads state to classify names the version of the state it read (stateVersion). The classification carries it, a request without If-Match is answered 428 and one whose If-Match names another version 412, both before the handler, and the handler’s write is conditional on that version (or loads, classifies and writes in one transaction under SELECT … FOR UPDATE), so it never writes state other than the state it classified.
  • The admin pipeline classifies every request after validation and before the handler. The handler receives the classification and cannot lower it, and every later stage reads the same one. The path parameters, query and body it classified are frozen: a stage that changes them fails the request. A binary body cannot be frozen, so the classification carries its SHA-256 digest (bodyDigest), which approval and apply bind to. The listener’s authorization stage (authorize) is required and runs on every request right after the classification.
  • Fail closed: when classify throws, meets a field it has no rule for, or returns an unknown member, effect, class or permission, or more than the operation declares, the request needs the operation as declared: security, with every declared effect and permission.
  • At boot, the admin listener refuses a route that names no registered operation, and one whose operationId, x-qauth-class, x-qauth-permission, x-qauth-effects or x-qauth-two-admin differs from the registry. The classification stage is registered before every route, and checks each route again on its first request. In CI, the API conventions check fails when an admin operation of the OpenAPI document disagrees with the registry.

These are not security operations:

  • Sending a set-up or reset link to the contact address already on the account. The link reaches only the person a self-service reset reaches. Copying the link is a security operation.
  • Brand strings and links. They are display data, under qauth:admin:write.
  • Anything outside the admin API. The developer API’s changes to a client the caller owns (ADR-012) and the account API’s changes to the caller’s own account keep their own rules.

No admin operation is registered yet.

Other areas register the operations below, and the Registered column says which are registered. When one is registered, the registry refuses it unless it is a security operation that carries at least the effects listed, and refuses an id that differs from one only in case. An operation may carry more effects. Only an operation marked “per request” may decide per request what it needs, with classify, and it lists every effect it may have; every other one needs its declaration on every request. Each entry is listed under each of its effects. The table lives in apps/auth-server/src/app/helpers/admin-operations-reserved.ts.

OperationEffectsPer requestRegisteredSourceDescription
users.links.copycredential-pathnonoADR-019 Decision 7, ADR-020 §3Copy a one-time set-up or reset link, returned once to the admin who copies it.
users.identifiers.updatecredential-pathnonoADR-019 Decision 7, ADR-020 §3Change an account’s identifier or contact address.
users.password.setcredential-pathnonoADR-019 Decision 7, ADR-020 §3Set an account’s password.
users.passkeys.createcredential-pathnonoADR-019 Decision 7, ADR-020 §3Add a passkey to an account.
users.credentials.createcredential-pathnonoADR-019 Decision 7, ADR-020 §3Add any other credential to an account, such as a TOTP secret or an upstream identity.
users.passkeys.deletecredential-pathnonoADR-019 Decision 7, ADR-020 §3Remove a passkey from an account.
users.totp.deletecredential-pathnonoADR-019 Decision 7, ADR-020 §3Remove an account’s TOTP.
users.recoveryCodes.deletecredential-pathnonoADR-019 Decision 7Remove an account’s recovery codes.
users.linkedAccounts.deletecredential-pathnonoADR-019 Decision 7Unlink an upstream or wallet account from an account.
users.idJagSubjects.createcredential-pathnonoADR-019 Decision 7, ADR-011 gate 14Link an enterprise ID-JAG subject to an account, the one link an admin may make.
upstreamProviders.createcredential-pathnonoADR-019 Decision 7, #714Add an upstream identity provider.
upstreamProviders.updatecredential-pathnonoADR-019 Decision 7, #714Change an upstream identity provider.
upstreamProviders.disablecredential-pathnonoADR-019 Decision 7, #714Disable an upstream identity provider.
upstreamProviders.deletecredential-pathnonoADR-019 Decision 7, #714Remove an upstream identity provider.
upstreamProviders.mappers.createcredential-pathnonoADR-019 Decision 7, #714Add a mapper to an upstream provider’s claims.
upstreamProviders.mappers.updatecredential-pathnonoADR-019 Decision 7, #714Change a mapper of an upstream provider’s claims.
upstreamProviders.mappers.deletecredential-pathnonoADR-019 Decision 7, #714Remove a mapper of an upstream provider’s claims.
trustedIssuers.createcredential-pathnonoADR-019 Decision 7Trust a credential issuer for wallet sign-in.
trustedIssuers.updatecredential-pathnonoADR-019 Decision 7Change a trusted credential issuer.
trustedIssuers.deletecredential-pathnonoADR-019 Decision 7Stop trusting a credential issuer.
plugins.enablecredential-pathnonoADR-019 Decision 7, #600Enable an AuthMethod plugin in a realm.
plugins.updatecredential-pathnonoADR-019 Decision 7, #600Change an AuthMethod plugin’s configuration in a realm.
realms.policies.updatecredential-path, token-content, posture-loosening, administrationyesnoADR-019 Decision 7, #514, #517Change realm policies: the mail transport and sender, the sign-in, password and brute-force policy, the registration policy, the environment ceiling, token and session lifetimes, the key rotation interval, audit-event retention and the two-admin set; lengthening a lifetime or the rotation interval, or shortening or switching off retention, is posture-loosening.
OperationEffectsPer requestRegisteredSourceDescription
realms.policies.updatecredential-path, token-content, posture-loosening, administrationyesnoADR-019 Decision 7, #514, #517Change realm policies: the mail transport and sender, the sign-in, password and brute-force policy, the registration policy, the environment ceiling, token and session lifetimes, the key rotation interval, audit-event retention and the two-admin set; lengthening a lifetime or the rotation interval, or shortening or switching off retention, is posture-loosening.
clients.createtoken-contentnonoADR-019 Decision 7, #518, #632Register a client, with its redirect URIs, authentication method and trust.
clients.updatetoken-content, posture-looseningyesnoADR-019 Decision 7, #518, #632Change a client: its redirect URIs, authentication method, first-party trust, verified domain, PKCE or DPoP, environment or agent-type fields.
clients.secret.rotatetoken-contentnonoADR-019 Decision 7, #518Rotate a client’s secret, returned once to the admin who rotates it.
clients.registrationToken.rotatetoken-contentnonoADR-019 Decision 7, #518Rotate a client’s registration access token (RFC 7592), returned once to the admin who rotates it.
clients.deletetoken-contentnonoADR-019 Decision 7, #518, #632Delete a client.
scopes.createtoken-contentnonoADR-019 Decision 7, ADR-020 §3Add a scope to the catalog.
scopes.updatetoken-contentnonoADR-019 Decision 7, ADR-020 §3Change a scope of the catalog.
scopes.deletetoken-contentnonoADR-019 Decision 7, ADR-020 §3Remove a scope from the catalog.
mappers.createtoken-contentnonoADR-019 Decision 7Add a token mapper.
mappers.updatetoken-contentnonoADR-019 Decision 7Change a token mapper.
mappers.deletetoken-contentnonoADR-019 Decision 7Remove a token mapper.
resourceServers.createtoken-contentnonoADR-019 Decision 7Register a resource server and its audience.
resourceServers.updatetoken-contentnonoADR-019 Decision 7Change a resource server or its audience.
resourceServers.deletetoken-contentnonoADR-019 Decision 7Remove a resource server and its audience.
roles.createtoken-contentnonoADR-019 Decision 7, #683Define a realm role.
roles.updatetoken-contentnonoADR-019 Decision 7, #683Change a realm role, its name or composites.
roles.deletetoken-contentnonoADR-019 Decision 7, #683Delete a realm role.
groups.createtoken-contentnonoADR-019 Decision 7, #683Define a group.
groups.updatetoken-contentnonoADR-019 Decision 7, #683Change a group.
groups.deletetoken-contentnonoADR-019 Decision 7, #683Delete a group.
groups.roles.assigntoken-contentnonoADR-019 Decision 7, #683Map a realm role to a group, so the group’s members hold it.
groups.roles.removetoken-contentnonoADR-019 Decision 7, #683Remove a realm role from a group.
users.roles.assigntoken-contentnonoADR-019 Decision 7, #683Assign a role to an account.
users.roles.removetoken-contentnonoADR-019 Decision 7, #683Remove a role from an account.
groups.members.addtoken-contentnonoADR-019 Decision 7, #683Add an account to a group.
groups.members.removetoken-contentnonoADR-019 Decision 7, #683Remove an account from a group.
webhooks.createtoken-contentnono#537Add a webhook subscription.
webhooks.updatetoken-contentnono#537Change a webhook subscription.
webhooks.deletetoken-contentnono#537Remove a webhook subscription.
webhooks.rotateSecrettoken-contentnono#537Rotate a webhook subscription’s signing secret.
webhooks.testtoken-contentnono#537Send a test event to a webhook subscription’s destination.
ssfStreams.createtoken-contentnono#537Add a Shared Signals stream.
ssfStreams.updatetoken-contentnono#537Change a Shared Signals stream’s destination.
ssfStreams.deletetoken-contentnono#537Remove a Shared Signals stream.
OperationEffectsPer requestRegisteredSourceDescription
users.actChainTokens.createissue-for-othernonoADR-019 Decision 7, #523Issue an act-chain token to an admin who acts for an account.
OperationEffectsPer requestRegisteredSourceDescription
realms.policies.updatecredential-path, token-content, posture-loosening, administrationyesnoADR-019 Decision 7, #514, #517Change realm policies: the mail transport and sender, the sign-in, password and brute-force policy, the registration policy, the environment ceiling, token and session lifetimes, the key rotation interval, audit-event retention and the two-admin set; lengthening a lifetime or the rotation interval, or shortening or switching off retention, is posture-loosening.
admins.createadministrationnonoADR-019 Decision 7, #679Create an admin account.
admins.deleteadministrationnonoADR-019 Decision 7, #679Remove an admin account.
adminGrants.createadministrationnonoADR-019 Decision 7, #679Grant an admin role to an account in a realm.
adminGrants.deleteadministrationnonoADR-019 Decision 7, #679Remove an admin role from an account.
adminRoles.createadministrationnonoADR-019 Decision 7, #679Create a custom admin role.
adminRoles.updateadministrationnonoADR-019 Decision 7, #679Change the permissions of a custom admin role.
adminRoles.deleteadministrationnonoADR-019 Decision 7, #679Delete a custom admin role.
serviceAccounts.createadministrationnonoADR-019 Decision 7, #679Create a service account for the admin API.
serviceAccounts.deleteadministrationnonoADR-019 Decision 7, #679Remove a service account.
OperationEffectsPer requestRegisteredSourceDescription
realms.imports.applyimportyesnoADR-019 Decisions 7 and 11, #531, #555Apply a realm-file import or a sandbox promotion.
OperationEffectsPer requestRegisteredSourceDescription
federation.entityConfiguration.updatefederation-operatornonoADR-019 Decision 7, ADR-020 §3Change the realm’s federation entity configuration and authority hints.
federation.subordinates.createfederation-operatornonoADR-019 Decision 7, ADR-020 §3Add a federation subordinate.
federation.subordinates.updatefederation-operatornonoADR-019 Decision 7, ADR-020 §3Change a federation subordinate.
federation.subordinates.deletefederation-operatornonoADR-019 Decision 7, ADR-020 §3Remove a federation subordinate.
federation.metadataPolicy.updatefederation-operatornonoADR-019 Decision 7, ADR-020 §3Change the federation metadata policy and its constraints.
federation.trustMarks.createfederation-operatornonoADR-019 Decision 7, ADR-020 §3Issue a trust mark.
federation.trustMarks.deletefederation-operatornonoADR-019 Decision 7, ADR-020 §3Revoke a trust mark.
federation.registrations.approvefederation-operatornonoADR-019 Decision 7, #731Approve an explicit federation registration.
federation.keys.rotatefederation-operator, key-custodynonoADR-019 Decision 7, ADR-020 §3Rotate the federation keys.
OperationEffectsPer requestRegisteredSourceDescription
federation.keys.rotatefederation-operator, key-custodynonoADR-019 Decision 7, ADR-020 §3Rotate the federation keys.
keys.rotatekey-custodynonoADR-019 Decision 9, #544Rotate a realm’s key before its rotation is due.
keys.retirekey-custodynonoADR-019 Decision 9Retire a key, which leaves the JWKS at once.
kek.rotatekey-custodynonoADR-019 Decision 9, #545Rotate the KEK and re-wrap every stored key.
OperationEffectsPer requestRegisteredSourceDescription
realms.deleterealm-lifecyclenonoADR-019 Decision 7Delete a realm.
realms.ownership.transferrealm-lifecyclenonoADR-019 Decision 7Transfer a realm’s ownership.
OperationEffectsPer requestRegisteredSourceDescription
agents.revokeauthority-tree-powernonoADR-019 Decision 7, ADR-014Revoke by agent: end every grant and token of an agent’s authority tree.
transmitters.disableauthority-tree-powernonoADR-019 Decision 7, ADR-014Disable a registered transmitter.
OperationEffectsPer requestRegisteredSourceDescription
realms.policies.updatecredential-path, token-content, posture-loosening, administrationyesnoADR-019 Decision 7, #514, #517Change realm policies: the mail transport and sender, the sign-in, password and brute-force policy, the registration policy, the environment ceiling, token and session lifetimes, the key rotation interval, audit-event retention and the two-admin set; lengthening a lifetime or the rotation interval, or shortening or switching off retention, is posture-loosening.
clients.updatetoken-content, posture-looseningyesnoADR-019 Decision 7, #518, #632Change a client: its redirect URIs, authentication method, first-party trust, verified domain, PKCE or DPoP, environment or agent-type fields.
OperationEffectsPer requestRegisteredSourceDescription
clients.sessions.revokeAllbulk-revocationnonoADR-020 §3, #625Revoke every session and refresh-token family of a client.
realms.sessions.revokeAllbulk-revocationnonoADR-020 §3, #550Revoke sessions of a realm in bulk.

An operation marked “per request” must meet its entry’s security cases: at boot, the registry runs its classify on each case and refuses the operation when a case gets less than the effects listed (it is then a security request), or fails closed because classify does not understand it. So a classify that answers write for every request cannot take the reservation. The change that registers the operation writes the cases in the representation it ships, and may add cases but never drop one or lower its effects.

  • lowers the password minimum length: at least credential-path.
  • switches brute-force protection off: at least credential-path.
  • raises the environment ceiling: at least posture-loosening.
  • lengthens the access-token lifetime: at least posture-loosening.
  • lengthens the refresh-token lifetime: at least posture-loosening.
  • lengthens the session lifetime: at least posture-loosening.
  • removes the session lifetime cap: at least posture-loosening.
  • lengthens the key rotation interval: at least posture-loosening.
  • shortens audit-event retention: at least posture-loosening.
  • switches audit-event retention off: at least posture-loosening.
  • adds a redirect URI: at least token-content.
  • sets the token endpoint authentication method to none: at least token-content.
  • replaces the JWKS: at least token-content.
  • moves the keys to a JWKS URI: at least token-content.
  • switches PKCE off: at least posture-loosening.
  • switches DPoP binding off: at least posture-loosening.
  • makes the client first-party: at least token-content.
  • loosens the environment: at least posture-loosening.
  • lowers the password policy: at least import.
  • adds a redirect URI to a client: at least import.

The effect tags and the operation ids will be part of the admin API’s contract. Like every QAuth-defined identifier, they are promised from 1.0.0 on (ADR-018 §5); until then a release may still rename one, and says so in its changelog. A reserved entry may be renamed by the change that registers its operation, but never weakened.