Skip to content
autonym.dev
ContentsHide contents

Core

A6. Authorisation

spec 1.0-draft-1

Status: normative. Document version 1.0-draft-1.

This chapter states the authorisation requirements against the abstract grant model, independently of any one authorisation protocol. Open Personal Context Core stays meaningful without PDPP: B1 is the normative 1.0 grant binding, not a dependency of the model. B1 maps these requirements onto PDPP Core; where B1 and this chapter differ, this chapter wins.

A6.1 Discovery and registration

Providers MUST serve a path-specific RFC 9728 metadata document for each protected resource they serve, at the location RFC 9728 section 3 derives from its resource identifier, such as /.well-known/oauth-protected-resource/mcp and /.well-known/oauth-protected-resource/v1, and authorisation-server metadata under RFC 8414. There is no document at /.well-known/oauth-protected-resource itself: its resource would be the origin, which is not an audience. Each document names the issuer in authorization_servers, sets dpop_bound_access_tokens_required to true and lists ES256 in dpop_signing_alg_values_supported. Metadata names the configured issuer, never an untrusted Host or forwarding header. Authorisation responses include the RFC 9207 iss parameter.

<origin>/mcp, <origin>/v1 and the resource of each binding a provider offers, such as the PDPP resource of B1.2, are distinct audiences. An unauthenticated response includes a WWW-Authenticate challenge pointing to the requested resource's metadata. Client ID Metadata Documents are the primary registration mechanism for unknown clients; static registration supports first-party clients and RFC 7591 dynamic registration remains available to any client for compatibility. Metadata fetching requires exact client-ID matching, redirect checks and SSRF-safe retrieval. A client cannot self-assign a trusted class or certification.

Registration metadata, sent to the registration endpoint or published as a Client ID Metadata Document, has the members of client-metadata.json or client-metadata-document.json: RFC 7591 members and autonym_purge_uri, the HTTPS URL to which purge instructions are delivered (A10). A client's keys are given inline in jwks under the A5.4 key-set rules: ES256 public keys whose key IDs are their RFC 7638 thumbprints, used for private_key_jwt client authentication and purge acknowledgements. The token endpoint authentication method is none for a public client or private_key_jwt for a confidential one; no client secret is issued or accepted. private_key_jwt needs jwks; client_credentials is only for a client that authenticates with private_key_jwt; refresh_token comes only with authorization_code, which goes with the code response type and at least one redirect URI; and redirect URIs, grant types and key IDs are each unique. A client that uses the A6.4 token exchange lists urn:ietf:params:oauth:grant-type:token-exchange in grant_types, and one that redeems a grant offer lists urn:autonym:params:oauth:grant-type:grant-offer; a grant type the provider does not offer is refused. A client that lists the grant offer grant type authenticates with private_key_jwt, as one that lists client_credentials does, because only its registered key shows that it is the client a grant offer names (A6.4); a public client may still use the token exchange as a delegate. A redirect URI is an HTTPS URL, or an http URL at 127.0.0.1, [::1] or localhost, without a fragment, and a port in any URL is between 1 and 65535. A Client Identifier URL has no . or .. path segment, written plainly or percent-encoded, and its host is a name whose last label is neither all digits nor 0x followed by hexadecimal digits, which URL parsers read as an IPv4 address. client_name, contacts, software_id and software_version carry only characters of the Basic Multilingual Plane, and no control character, bidirectional formatting character, line or paragraph separator, interlinear annotation character or default-ignorable character, one that renders as nothing, such as a zero-width space, a soft hyphen, a Hangul filler or a variation selector. Characters outside the Basic Multilingual Plane are refused because they include the tag characters, which can carry hidden text, and a regular expression that reads text in 16-bit code units, as JavaScript's does, cannot tell those from the rest. jwks_uri, sector_identifier_uri, application_type, a software statement, a registry attestation and any member the schema does not list are refused, so a registration is never accepted with members silently dropped; whether a client may use the loopback-port exception is decided from its redirect URIs (A6.3). The registration endpoint answers RFC 7591 invalid_redirect_uri or invalid_client_metadata for metadata it refuses, and answers 429 rate_limited in the RFC 7591 error form to a source that registers too often.

A6.2 Scopes and separate grants

scopes.json and scope grammar define the existing autonym: syntax, ten purpose categories and exactly one purpose. A purpose alone gives no read, contribution, persistence or delegation right. Any accepted :declared modifier makes the entire stored grant declared-only, including wildcard combinations; use separate grants for different declaration restrictions. Explicit domain ceilings take precedence over wildcard L0. Scope parsing is syntactic; the issuance policy additionally rejects a mixed read/contribution grant.

Each capability scope has one meaning at issuance (scope grammar): autonym:propose requests a contribution grant, and autonym:confirm sets its allow_confirmation; autonym:persist permits a read grant's persist_until, and autonym:delegate sets its allow_delegation. autonym:confirm:safety, autonym:write:declared, autonym:log:read and autonym:manage belong to the person's own first-party clients and are never issued to a recipient. openid and offline_access are not Open Personal Context scopes, so a request naming either is refused like any other unknown scope. A request that mixes read and contribution, asks for a capability of the other grant kind or asks for one its client's class cannot hold (A2) fails OAuth invalid_scope; a contribution detail that does not follow its schema, or repeats a domain or a key, fails RFC 9396 invalid_authorization_details.

A contribution grant is requested with autonym:propose, exactly one purpose scope and one RFC 9396 authorization_details entry of type urn:autonym:authorization-details:contribute (contribution-detail.json), which names the domain and key allowlist, max_events and allow_confirmation. allow_confirmation is true exactly when autonym:confirm is requested; a detail that says otherwise fails RFC 9396 invalid_authorization_details, so the class limits on autonym:confirm (A2) also bound the detail. The provider generates its stream_id at issuance. A contribution grant is never a PDPP grant (B1.1).

The grant schema is a discriminated read or contribute object. A read grant names domain ceilings and explicit keys, declared-only/confidence restrictions, maximum reads, retention and delegation choice. A contribution grant names an explicit domain/key allowlist, event count and confirmation permission. They have distinct IDs, approval, tokens and revocation. Neither implies the other. An operation that the authenticated grant's kind or permissions do not allow fails capability_not_granted. Contributor tokens can inspect only their current grant and their own submission status.

The stored grant is authoritative for restrictions, status, expiry, counters and revision. Token scope and copied claims are advisory. Every read, including L0 and a conditional request, rechecks it. A revision changes whenever authority changes. expires_at must be after issuance; read/event counts cannot exceed their maximum. Persistence cannot exceed grant expiry. A wildcard is allowed only as a read domain at L0, never for contribution or advertising; wildcard permission is expanded against the current core vocabulary and remains subject to grant review. Every grant has exactly one audience, the resource it was requested for, and its tokens are valid only there. A grant is continuous when it can serve more than one action: a read grant whose max_reads is above 1, or a contribution grant whose max_events is above 1.

Use authorisation code with PKCE S256 only; reject plain/downgrade. Require exact registered redirect-URI matching, with only the RFC 8252 loopback-port exception, which applies only to a client whose registered redirect URIs are all http loopback URIs: a redirect to one of them may name any port. A client that registers any other redirect URI gets exact matching for every one. Codes are single-use and bound to client, redirect, resource, session and verifier. Use session-bound state, unframeable consent pages and rotating refresh tokens with replay detection. Refresh tokens are issued only for a continuous grant (A6.2), rotate on every use and never outlive the grant. A second use of an authorisation code or of a spent refresh token revokes the grant it belongs to, and that request fails OAuth invalid_grant. Consent shows the named recipient, purpose category and plain-language explanation, read or contribution permission, selected keys/levels, expiry, limits, retention and trust evidence. Each choice is independently revocable. Purpose changes require new consent. Refusing advertising cannot revoke an unrelated non-advertising grant. Recipient-side consent flags cannot substitute for the provider's decision.

Access tokens follow RFC 9068, with the claims of access-token-claims.json: a JWT of typ at+jwt, signed with ES256 under the authorisation server's token keys, which are never A5.4 signing keys. Example claim fragment:

json
{
  "iss": "https://autonym.example",
  "aud": "https://autonym.example/v1",
  "iat": 1789948800,
  "exp": 1789952400
}

These timestamps represent 21 September 2026 00:00 to 01:00 UTC. Full tokens also carry sub, the pairwise handle (A3.2); client_id; jti; cnf.jkt, the thumbprint of the client's DPoP key; autonym_grant_id; and autonym_purpose, the grant's purpose category. A token for a grant requested with scopes carries their canonical scope, and one for a grant requested with an authorisation detail carries authorization_details. aud names exactly one resource, the grant's. Access tokens last at most one hour and never beyond grant expiry. Refresh tokens cannot extend a grant. Check issuer, audience, signature, expiry and server-side grant on every authorised action.

Every access token, for every client class and every resource, is bound to the client's DPoP key under RFC 9449: no Bearer token is issued or accepted. A token request without a valid proof, or a pushed authorisation request with an invalid one, fails RFC 9449 invalid_dpop_proof, and one whose proof carries a stale nonce fails use_dpop_nonce, with a fresh nonce. A proof is accepted only once, and only within 60 seconds of its iat. At a resource, a bound token is sent as Authorization: DPoP with a proof that binds the method, the URL and the access token, signed by the key whose RFC 7638 thumbprint is the token's cnf.jkt; a proof signed by any other key fails dpop_invalid. Never accept a bound token as Bearer. A request to a resource without a proof fails dpop_required; an invalid, stale or replayed proof fails dpop_invalid. An assistant_unverified grant lasts at most 30 days and cannot persist, delegate or confirm. No third party receives autonym:confirm:safety or arbitrary declared-write authority.

OAuth endpoints answer in their own error form. The authorisation endpoint and the pushed authorisation request endpoint (RFC 9126) return invalid_request, invalid_client, unauthorized_client, access_denied, unsupported_response_type, invalid_scope, invalid_target, invalid_request_uri, invalid_authorization_details or server_error. A request with prompt=none that needs the person returns login_required, consent_required or interaction_required. The token endpoint returns invalid_request, invalid_client, invalid_grant, unauthorized_client, unsupported_grant_type, invalid_scope, invalid_target or server_error. At a resource, a token whose signature, issuer, audience, type, lifetime or claims fail verification fails RFC 6750 invalid_token with status 401 and the A6.1 challenge. A verified token whose grant has ended, or whose client is suspended, fails that registered code instead (A5.2, A6.8).

A6.4 Standing rules and delegation

Delegated is an optional provider capability. A standing rule is a person-approved exact match on client IDs or registry-independent approved classes, allowed purpose categories, domain/key/level limits, maximum duration, read count, declared-only/confidence and persistence. Unknown matching fields fail closed. The provider records the rule version, creates a real child grant and notifies the person. Version 1 standing rules cannot issue advertising or contribution grants automatically.

A read grant may delegate only if its stored allow_delegation is true. The child can only narrow purpose, domains, keys, ceilings, confidence, expiry, retention and read limits. The child consumes a reserved portion of its parent's remaining read budget; no child can multiply authority. Revoke/expire the parent and all descendants become unusable. Depth is at most one; children cannot delegate again.

At the token endpoint, RFC 8693 token exchange requires grant_type=urn:ietf:params:oauth:grant-type:token-exchange, subject_token, subject_token_type=urn:ietf:params:oauth:token-type:access_token, requested_token_type=urn:autonym:params:oauth:token-type:grant-offer, scope, resource, autonym_recipient, autonym_purpose_text, autonym_expires_in and autonym_max_reads. Authenticate the delegate and recheck its current grant. The request's DPoP proof is signed by the key whose thumbprint is the subject_token's cnf.jkt, or the request fails OAuth invalid_grant. autonym_recipient names a client registered with the grant offer grant type, which therefore authenticates with private_key_jwt (A6.1); naming any other client fails, as a missing parameter does. Missing or widening parameters fail; no default broadening is permitted.

The response is {access_token:<opaque-offer>,issued_token_type:"urn:autonym:params:oauth:token-type:grant-offer",token_type:"N_A",expires_in:300}. The opaque offer contains at least 256 random bits, is stored hashed, expires after five minutes or parent expiry, and is not a resource token. The recipient redeems it through the RFC 6749 extension grant grant_type=urn:autonym:params:oauth:grant-type:grant-offer, grant_code and exact resource, authenticated as the target client with private_key_jwt and presenting its own DPoP key. Holding the offer is never enough to redeem it. Bind offer to target, audience and proposed child restrictions. Check parent, budget and one-use state again atomically at redemption. Invalid, used or expired offers return OAuth invalid_grant. The resulting access token has act.client_id naming the delegate. This exchange contract is an Autonym choice, not a claim that RFC 8693 defines grant offers.

A6.5 Revocation

POST /oauth/revoke follows RFC 7009 for refresh tokens, and revoking a refresh token revokes the grant it belongs to. A public client's refresh token is bound to its DPoP key (RFC 9449 section 5), so revoking one needs proof of possession, as using one does: a public client's revocation request carries a DPoP proof of that request, checked as at the token endpoint (A6.3), and without a valid one it fails RFC 9449 invalid_dpop_proof, or fails use_dpop_nonce with a fresh nonce when its nonce is stale, and revokes nothing. A refresh token bound to a key other than the proof's is not revoked, and the request is answered as RFC 7009 answers an invalid token, so the answer does not tell the holder of a stolen refresh token whether it is live. A confidential client's revocation request is authenticated with private_key_jwt (A6.1). An access token is not revoked on its own: the endpoint answers RFC 7009 unsupported_token_type for one, and it ends with its grant, because every authorised action checks the stored grant (A6.3). The owner's grant-revocation action atomically invalidates all associated authority and descendants, records the event and enqueues required purge instructions. Revocation takes effect on the next attempted action, including retries and cached/conditional reads. No background token-expiry job is the enforcement boundary.

Checks: CORE-01, CONSENT-01, OAUTH-01, FETCH-01, SCOPE-01 to SCOPE-09, DISC-01, ADS-01, DELEGATE-01, TRANSPORT-01, PURGE-01.

A6.6 Client lifecycle credentials

Purge polling/acknowledgement continues after record grants end. A registered confidential recipient uses OAuth client credentials with private-key authentication and DPoP to request only scope=autonym:lifecycle:purge and resource <origin>/v1. This is a client lifecycle token, not a record grant: its JWT (lifecycle-token-claims.json) has autonym_token_kind:lifecycle, sub and client_id, both the client's ID as RFC 9068 asks of a client credentials token, scope autonym:lifecycle:purge, issuer/audience, cnf.jkt, iat, exp and jti, but no handle or grant ID. Maximum lifetime is five minutes. It is valid only on the two purge routes and can access only that client's instructions. It cannot satisfy a record-grant check, discover records or create grants. The lifecycle scope is deliberately outside the record-grant scope parser and never appears in a consent grant. Public clients without a registered private key cannot use polling credentials; the provider uses registered callback delivery or the owner's visible manual follow-up, recording unavailable acknowledgement assurance.

The deployment requirements in A16 also apply. Advertising requires the local-law and opt-out controls in PRIVACY-05 as well as ADS-01 and ADS-02.

Read and contribution permissions require separate grants, approval and revocation. One named recipient, one purpose category and a plain-language purpose explanation are required. A recipient's consent manager may initiate a flow, but only the provider's stored active grant authorises access. Recommendation/personalisation permission MUST NOT be repurposed for advertising. Declining advertising MUST leave independent non-advertising permissions intact.

Advertising is optional and off by default. Version 1 permits only explicitly person-confirmed viewing.genre_affinity, reading.genres and music.genres, validated against their closed core enums. All other keys are ineligible. No inferred preferences, free text, activity history, extensions, wildcard domains, sensitive or safety information, identifiers, location, financial, employment or proxy categories enter this lane. A declared value derived from sensitive source material is not made eligible by relabelling it. Providers MUST require the person to choose the offered ordinary preference directly and prohibit sensitive targeting/inference by recipient policy.

An advertising read grant requires independent first-party consent, a self-directed adult-owned record, an active eligible named recipient, explicit key selection, declared-only mode, maximum L1, exactly one permitted read, expiry within 24 hours, no persistence, delegation or onward-sharing right. If adult/self-directed eligibility cannot be established, deny the feature; do not collect extra identity documents simply to enable advertising. An advertising read for which the recipient's eligibility cannot be established fails advertising_ineligible; the code never reveals that the person's or the record's eligibility has changed. Child/guardian-managed records are excluded. Recipient suspension stops reads immediately. Retain context only for the current interaction and never past the signed five-minute maximum; a new interaction needs fresh permission.

The consent view shows selected context, recipient, advertising purpose, expiry and no-retention terms. Refusal is an ordinary choice. No resale, advertising identifier, cross-recipient correlation, provider monetisation of contents or undisclosed ad-exchange disclosure is implied. Contextual ads that read no record need no Autonym read grant. Off-platform misuse cannot be prevented once data is disclosed; recipient terms and independent assurance remain necessary. These rules are not a legal-compliance certificate.

Every advertising read has the same signed envelope and receipt as other reads. Withdrawal and purge state are visible to the person. Purge acknowledgement is an assertion, not proof that every copy has gone.

Checks: CONSENT-01, ADS-01, ADS-02, SCOPE-10, PRIVACY-05.

A6.8 Client standing and safety purposes

A suspended client takes no recipient action, even with an otherwise active grant, except at the A6.6 purge routes: it is not authorised, obtains and refreshes no token, and its request to a resource fails client_suspended. The child grants of a suspended client's delegations (A6.4) are unusable while it is suspended. A suspension revokes nothing, so a resumed client's unexpired grants work again; a deregistered client's grants are revoked. The person sees the recipient's registered identity and available evidence during consent.

A normal v1 safety or medical purpose still requires a current recipient grant, the same compiler and a receipt. There is no anonymous emergency override, special URL that bypasses consent, or promise of emergency-service availability.

Checks: CORE-01, ADS-01, CLAIM-01.

References to other standards and projects describe intended technical alignment only and do not imply endorsement or partnership.