# Machines, agents and assistants

`https://accounts.praecise.com`

For anything that relies on the Praecise account and is not a person in a
product's browser tab: a server being enrolled, an agent that runs
unattended, an assistant acting for a person, an API that accepts this
service's tokens. A product signing people in is in [sign-in.md](sign-in.md);
the shared services and the detail of tokens for an API are in
[api.md](api.md).

Everything here is discoverable without this page: the metadata is at
`/.well-known/openid-configuration` (and, with the same content,
`/.well-known/oauth-authorization-server`) and the keys at `/jwks`.

## Which one are you

| You are | You use | A person is involved |
|---|---|---|
| A program acting as one account on the shared services (domains, mailboxes) | a service token, sent as `Authorization: Bearer` to `/api` ([api.md](api.md)) | once, to mint the token on their account page |
| A machine with no browser that should belong to an account | the device grant | once, to approve it on another device |
| An agent that runs with nobody present | the client credentials grant, for one API | never; the API decides what the agent may do |
| An assistant or tool acting for a person (an MCP host, a command line) | authorization code with PKCE, as a client described by a metadata document or registered by itself | each time, on a consent page |
| An API that accepts these tokens | the JWKS, and optionally a claims hook | no |

## A machine with no browser: the device grant (RFC 8628)

The client is `furcate-node`, public, with no secret.

1. `POST /device/auth` with `client_id=furcate-node&scope=openid`. The answer
   holds `device_code`, `user_code` (eight letters, shown as `XXXX-XXXX`),
   `verification_uri` (`/device`), `verification_uri_complete`, `interval`
   and `expires_in` (five minutes).
2. Optionally say what is being claimed, so the person approving sees it and
   not only a client name: `POST /device/enrolment` with JSON
   `{"user_code", "node_id", "site", "site_addr", "machines": [{"node_id", "name", "attested", "administers"}]}`
   (at most 64 machines). It grants nothing; it only fills the approval
   screen for that code.
3. Show the person the code and the address. They open it on a device with a
   browser, sign in, and approve what the screen lists.
4. Poll `POST /token` with
   `grant_type=urn:ietf:params:oauth:grant-type:device_code&device_code=…&client_id=furcate-node`,
   no faster than `interval`. `authorization_pending` means not yet;
   `expired_token` or `access_denied` means start again.

## An agent with nobody present: client credentials

An agent client is an entry in the service's `AGENT_CLIENTS` setting (an id
and a secret of at least 32 characters; optionally `dpop_required`). It may
ask for a token for one API, and only an API that has a claims hook, because
the API, not this service, says what that agent is and may do:

```
POST /token
Authorization: Basic base64(<id>:<secret>)

grant_type=client_credentials&resource=<the API's address>&scope=<its scopes>&<the API's own parameter>
```

The token is refused unless the API's hook approves this client for it. It
lasts ten minutes at most. No other kind of client may use this grant.

## An assistant acting for a person

Authorization code with PKCE (S256), as a public client, identified in one of
two ways:

- **A metadata document.** The `client_id` is an HTTPS URL and the JSON
  document there is the registration (the form the MCP authorization
  specification asks for). It is fetched over HTTPS from a public host,
  without following redirects, at most 5 kB within 2.5 seconds; its
  `client_id` must be the URL it was fetched from; it must name the client and
  its redirect addresses; a loopback redirect address may use any port.
- **Registering itself** (RFC 7591): `POST /reg` with its metadata. The answer
  holds the `client_id` and a token with which it can read, change and delete
  its own registration.

Either way the person signs in and sees a consent page naming the assistant
(for a document, its host) and exactly what it asked for; nothing is granted
that was not shown. One that asks only who the person is (`openid`, `email`,
`profile`) is shown a plain page listing what it will be told; within one
Praecise sign-in it is asked once, and a silent request (`prompt=none`) before
that is answered `consent_required`. Such a client never gets the client credentials grant.

A token is for exactly one API: name it with `resource` on the authorization
request (RFC 8707), ask for that API's scopes, and send the token only there.
What the API's token carries, how long it lasts and how it is refreshed is in
[api.md](api.md). Add `offline_access` to stay connected; a refresh token for
an API with a hook is replaced at every use, and a second use of an old one
ends the grant.

## Binding a token to a key (DPoP, RFC 9449)

Any client may send a DPoP proof with its token request (ES256 or EdDSA). The
access token then carries the key's thumbprint in `cnf.jkt`, and the API asks
for a proof with every call. A client marked `dpop_required` is refused a
token without one.

## Ending a token

- `POST /token/revocation` ends a refresh token and its grant. A JWT access
  token cannot be revoked; it ends when it expires (fifteen minutes at most
  for an API with a hook).
- `POST /token/introspection` says whether a token is active, for an API that
  cannot verify a JWT itself.
- A person ends everything a browser holds with "Sign out everywhere" on
  their account page; service tokens, enrolled machines and connected
  assistants are ended one by one there, not by that.

## An API that accepts these tokens

Verify the JWT against `/jwks`: `iss` is exactly the issuer, `aud` is exactly
your own address, `exp` has not passed, and, when the token is bound,
the DPoP proof matches `cnf.jkt`. To decide yourself what a token for you may
say (which account at your service, which permissions), offer a claims hook:
this service asks it at every issuance and refresh and issues nothing when it
refuses, fails or times out. The hook's request, signature and answers are in
[api.md](api.md).

An ID token from a fresh passkey confirmation can serve as evidence for one
action: the product sends `nonce` tied to the action, and the API checks
`iss`, `aud` (the product's client id), `nonce`, `auth_time`, `acr: "phr"`,
`amr` and `sub` itself ([sign-in.md](sign-in.md), "Asking for a passkey").

## What does not exist

- No password grant, no implicit flow, no sign-in by emailed code, no sign-in
  by passkey alone.
- No way for a program to make an account: accounts are made by a person,
  with an invitation and a code sent to their mailbox.
- No way for a program to mint its first service token: a person does that on
  their account page.
