# OAuth 2.1 Authorization Server An application exposes an authorization server by annotating one, and none without it. A client authorized this way holds a token of the identity that consented, and presents it as `Bearer`. ## Annotation ```yaml exposition: authorities: local: api.example.com oauth: authorize: https://app.example.com/oauth/authorize resources: ['/.mcp'] scopes: [app:notes, app:notes:read] registration: closed ```
authorize
Required. The absolute URL of the consent page. The application serves it; see Consent.
resources
Paths advertised as protected resources, each with a document of its own and named by the challenge a request to it is refused with. /.mcp is where MCP is served. A token is not yet bound to one, see Audience.
scopes
Advertised as what a client may ask for. Each is a role, or a scope within one.
registration
open advertises the registration endpoint, closed does not. Defaults to closed.
The issuer is the authority's configured host, over `https` unless the host is a loopback one. ## Discovery Two documents are served, at the paths their specifications fix, to anyone. `/.well-known/oauth-authorization-server` is [RFC 8414](https://www.rfc-editor.org/rfc/rfc8414) metadata: ```yaml issuer: https://api.example.com authorization_endpoint: https://app.example.com/oauth/authorize token_endpoint: https://api.example.com/identity/grants/ registration_endpoint: https://api.example.com/identity/clients/ response_types_supported: [code] grant_types_supported: [authorization_code] code_challenge_methods_supported: [S256] token_endpoint_auth_methods_supported: [none] client_id_metadata_document_supported: true authorization_response_iss_parameter_supported: true ``` `/.well-known/oauth-protected-resource` is [RFC 9728](https://www.rfc-editor.org/rfc/rfc9728) metadata. Each configured resource has one of its own, at the path the well-known segment is followed by: `/.mcp` is read at `/.well-known/oauth-protected-resource/.mcp`. ```yaml resource: https://api.example.com/.mcp authorization_servers: [https://api.example.com] bearer_methods_supported: [header] ``` `/.well-known/openid-configuration` answers with the authorization server metadata, for a client that reads only that. A reply of `401` carries the challenge that names the document: ```http 401 Unauthorized www-authenticate: Bearer resource_metadata="https://api.example.com/.well-known/oauth-protected-resource/.mcp", scope="app:notes" ``` ## Clients A `client_id` that is an `https` URL is a [Client ID Metadata Document](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-client-id-metadata-document-00): the client publishes what it is at that URL, and the document is read from there. Only origins `identity.clients` is configured to trust are read, and an empty list reads none: ```yaml configuration: identity.clients: trust: - https://claude.ai ``` Anything else was registered through [RFC 7591](https://www.rfc-editor.org/rfc/rfc7591), which `registration: open` advertises: ```http POST /identity/clients/ HTTP/1.1 content-type: application/json {"client_name": "Claude", "redirect_uris": ["https://claude.ai/api/mcp/auth_callback"]} ``` ```http 201 Created {"client_id": "c8f3a1e4d8c9b2f6a0e5d7c3b1a9f8e2", "client_name": "Claude", …} ``` A registration is addressed by a hash of what it says, so the same metadata is one client however many times it arrives. A registration cannot be changed: a revision is a different client. Only public clients are supported. `token_endpoint_auth_method` other than `none` is refused. `redirect_uris` are matched exactly, except for a loopback address, where the port is ignored ([RFC 8252 §7.3](https://datatracker.ietf.org/doc/html/rfc8252#section-7.3)). `GET /identity/clients/:id/` returns a client to any authenticated identity, which is what the consent page reads. ## Token endpoint `POST /identity/grants/`, anonymous, `application/x-www-form-urlencoded`. ```http POST /identity/grants/ HTTP/1.1 content-type: application/x-www-form-urlencoded grant_type=authorization_code&code=SplxlO&code_verifier=dBjftJeZ&redirect_uri=…&client_id=… ``` ```http 200 OK cache-control: no-store {"access_token": "…", "token_type": "Bearer", "expires_in": 2592000} ``` `code_verifier` is required: the code was issued against its `S256` hash ([RFC 7636](https://www.rfc-editor.org/rfc/rfc7636)). A code may be redeemed once, and is spent whether or not the verifier matches. Every refusal is `invalid_grant`. A code is valid for `identity.grants.lifetime` seconds, 60 by default. A token is valid for `identity.grants.token` seconds, 30 days by default; `0` is until it is revoked. ## Grants A grant is what a user allowed a client. One is held per identity per client. `GET /identity/grants/:identity/` lists them. `DELETE /identity/grants/:identity/:id/` revokes one, which disables the key its token was issued under: the token stops being one within `identity.tokens.cache.ttl`. ## Tokens An access token is a token of the identity that consented, carrying the [roles](access.md#roles) that identity holds, or the subset the client asked for as `scope`. It is presented as `Bearer`, which `identity.federation` also answers to — see [Bearer scheme](identity.md#bearer-scheme). A `scope` a client asks for is not checked against `scopes_supported`: it is granted if the consenting identity holds it, and refused as `invalid_scope` otherwise. What is advertised says what an application means to offer, not what the server will refuse. ## Audience `resource` ([RFC 8707](https://www.rfc-editor.org/rfc/rfc8707)) is accepted at both endpoints and recorded on the grant. **It does not yet restrict the token.** An access token carries the rights of the identity that consented, over every path that identity may reach, whichever resource it was asked for. Restricting it is a `permissions` argument at the `identity.tokens.issue` call in `identity.grants.exchange` — `{'/.mcp': ['*']}` for a resource at `/.mcp` — enforced by `permits()`, which is already what reads a token's permissions on every request. It waits on the resource that will check the audience itself. ## References - [OAuth 2.1](https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1) - [Consent](consent.md), the page an application serves - [Features](../features/oauth.grants.feature)