# auth.md

You are an agent. This service supports agentic registration: discover → register → (claim if needed) → exchange for an access_token → call API → handle revocation. Follow the steps in order. Do not skip ahead.

Resource server: `https://sma-das.blog/api/v1`. Authorization server: `https://sma-das.blog`. Public GET on the research API works without a token. A token is optional identity.

## Discover

Discovery is two hops. You may already have the 401.

The 401 response that pointed you here also carries a `WWW-Authenticate` header with the PRM URL:

```
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer resource_metadata="https://sma-das.blog/.well-known/oauth-protected-resource"
```

Pull the `resource_metadata` value and fetch it. If you do not have the 401, GET `https://sma-das.blog/.well-known/oauth-protected-resource`.

### Fetch the Protected Resource Metadata

GET /.well-known/oauth-protected-resource

Fields: `resource` is the API audience (`https://sma-das.blog/api/v1`). `authorization_servers` lists this origin. `scopes_supported` is `research.read` and `research.search`. `bearer_methods_supported` is `header`.

### Fetch the Authorization Server metadata

GET https://sma-das.blog/.well-known/oauth-authorization-server

Read `issuer`, `token_endpoint`, `revocation_endpoint`, `grant_types_supported`, and the `agent_auth` block in full. `agent_auth.skill` is this document. `agent_auth.identity_endpoint` is `https://sma-das.blog/agent/identity`. `agent_auth.claim_endpoint` is `https://sma-das.blog/agent/identity/claim`. `agent_auth.events_endpoint` is `https://sma-das.blog/agent/event/notify`. `identity_types_supported` is `anonymous`, `identity_assertion`, and `service_auth`. For `identity_assertion`, `assertion_types_supported` is the ID-JAG URN `urn:ietf:params:oauth:token-type:id-jag`. That URN belongs inside `identity_assertion.assertion_types_supported`, not at the top level.

## Pick a method

1. You have a session tied to a user identity and can exchange it for an ID-JAG, audience-bound to this service → identity_assertion + id-jag. This host does not keep an issuer trust list, so identity_assertion currently returns `issuer_not_enabled`. Fall back to anonymous.
2. You have only the user's email → service_auth. This host has no end-user accounts, so service_auth returns `service_auth_not_enabled`.
3. You have neither → anonymous. This is the working method. Claim is optional and does not add scopes.

## Register

Before sending an identity_assertion or service_auth body, surface `resource_name` and `resource_logo_uri` from the PRM. Skip that for anonymous.

### identity_assertion + id-jag

POST /agent/identity with `type: identity_assertion`, `assertion_type: urn:ietf:params:oauth:token-type:id-jag`, and `assertion`. Expect `issuer_not_enabled` until a trust list exists.

### service_auth

POST /agent/identity with `type: service_auth` and `login_hint`. Expect `service_auth_not_enabled`.

### anonymous

POST /agent/identity

```
{ "type": "anonymous" }
```

Keep `identity_assertion`. Go to Exchange. `claim_token` is returned once if you want the unused claim path.

## Claim

Anonymous tokens already carry `research.read` and `research.search`. Skip this step unless you are completing a claim ceremony from another profile. POST /agent/identity/claim returns `claimed_or_in_flight`. The human page is [https://sma-das.blog/claim](https://sma-das.blog/claim).

## Exchange

POST the `identity_assertion` to the token_endpoint with the jwt-bearer grant.

```
POST /oauth2/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<identity_assertion>
&resource=https://sma-das.blog/api/v1
```

Extract `access_token`.

## Use the access_token

```
GET /api/v1/posts
Authorization: Bearer <access_token>
```

GET without Authorization also succeeds for public research. Invalid Bearer values receive 401 with `WWW-Authenticate`. Full API reference: [https://sma-das.blog/developers](https://sma-das.blog/developers) and [https://sma-das.blog/openapi.json](https://sma-das.blog/openapi.json).

## Errors

| Code | Where | What to do |
| --- | --- | --- |
| anonymous_not_enabled | /agent/identity | Pick another method. |
| service_auth_not_enabled | /agent/identity | Use anonymous. |
| issuer_not_enabled | /agent/identity | Use anonymous. |
| invalid_request | /agent/identity | Fix the body. |
| claimed_or_in_flight | /agent/identity/claim | Exchange the identity_assertion instead. |
| invalid_grant | /oauth2/token | Register again at /agent/identity. |
| unsupported_grant_type | /oauth2/token | Use jwt-bearer or the claim grant. |
| authorization_pending | /oauth2/token | Honor interval. This host does not complete claims. |

Retry policy: 5xx, backoff. 4xx, do not retry the same payload. 401 on a working token, retry Exchange once, then restart at Discover.

## Revocation

POST `token=<access_token>&token_type_hint=access_token` to the revocation_endpoint. RFC 7009, 200 and idempotent. Re-run Exchange for a fresh access_token. The events_endpoint accepts provider-posted Security Event Tokens. You do not call it. On `invalid_grant`, restart at Register.
