Identity and Agent Registration
What every platform with accounts has to get right is the first five minutes: a person says "sign me in to example.com" to an agent that has never heard of the service, and a few moments later the agent is working in that person's account. This page gives that moment one vocabulary and one flow, so that implementers do not invent either under pressure.
Non-normative. A service that needs no authentication ignores this section.
BEST defines no identity provider, no account model and no login. A platform that has them keeps them; the one thing BEST fixes is how an agent obtains its credential (Agent Registration). This section names the parts so that implementers use the same words for the same things. Worked example: specs/identity-and-registration.md.
Who is involved. The person the agent works for. The agent — the BEST consumer. The account — the scope a credential opens (a tenant, in a multi-tenant host). The approver — whoever may admit an agent to an account: the person themselves on a self-serve platform, an administrator on a provisioned one.
The flow is one, whatever the person said and whether or not they have an account. The person says "sign me in to example.com" — or "sign me up", or "connect me". The agent registers itself and gives the person a link and a code. The link opens a page of the platform where the person signs in, or signs up if the platform offers that, and approves the agent. The agent collects its credential. The agent never needs to know whether an account existed.
The behaviours, told apart by whose they are:
| Behaviour | Whose | Where it happens |
|---|---|---|
| Register agent | the agent's | the device authorization request (Agent Registration) — not a BEST command |
| Sign in | the person's | the platform's own page, reached from the link the agent shows |
| Sign up (where the platform offers it) | the person's | the same page; it is also where the account is opened |
| Approve agent | the approver's | the same page, once signed in |
| Revoke agent | the agent's, or the approver's | a command under that credential; or the platform's UI |
| Sign out | the person's | the platform's own UI; it ends the person's browser session and touches no agent credential |
Keep them apart. Signing in, up and out belong to the person and happen on the platform's pages, with its identity provider: the agent never sees a password, never performs or simulates them, and no BEST operation or workflow is named after them. Registering and revoking belong to the agent and are named as such: an operation that revokes a key is a revocation, not a sign-out. Ending the person's session leaves every agent credential alive; revoking a credential leaves the person's session alive.
The flow, seen from both sides:
- The agent posts to
deviceAuthorizationUrland at once holds a secret the service generated, and a link and a short code for the person. - The agent gives the person the link and the code before anything else, to open in their own browser; it never opens the link itself. Until the person acts, nothing else can happen.
- The person opens the link. The page lets them sign in — or sign up, where the platform offers it — and then approve the agent. Nothing the agent sent has any effect without that, and unapproved, the registration expires.
- Meanwhile the agent polls
tokenUrl. On approval the answer is the credential and the account's identifier — and from that, the account's manifest: everything the next request needs.
Whether a person without an account can get one is the page's business, not the agent's: a self-serve platform offers sign-up there, a platform with provisioned accounts offers sign-in only and its approver may be an administrator. The agent's flow is the same in both. A platform may equally offer none of this and issue credentials out of band — it then omits deviceAuthorizationUrl.
Where the credential lives is the consumer's business, and only the consumer knows. A client with its own store — the reference MCP server is one — makes the token request itself, keeps the credential where the model never sees it, and has it again at every later start: the person registers once. An agent with no such client — a model in a chat making HTTP requests — has nowhere to put a secret but the conversation. It registers with credential_lifetime=session; the approval page tells the person what is being granted ("until you revoke it", or "for eight hours"); and what it receives is short-lived, so the next conversation starts with a new link and code rather than with a key left in an old one. Whatever the consumer, the sign-in guidance tells it at every step that the credential is a secret, and the token answer's credential block offers both ways to use it — configuring an MCP client, or direct calls; which one, and whether the connection lasts, is the consumer's decision.
Revoke agent is an ordinary command on the account's surface, sent under the credential being revoked. After it, that credential receives 401, and the way back is the one the manifest declares. What an open stream does then, and a bound the approver sets on a credential's life, are in Agent Registration (a credential that ends).
Optional, and outside this flow: a platform may let an agent open an account itself. That is an ordinary command carrying impact: commitment, confirmed with the person before it is sent; nothing above depends on it.
What "sign me in" means
A recipe called "sign in" whose steps register an agent is the usual mistake. When the person says "sign me in" they are saying what they want; what the agent does is register.
The exchange
Registration is RFC 8628 — the OAuth 2.0 Device Authorization Grant — unchanged. The manifest declares the two endpoints:
"authentication": {
"type": "apiKey", "scheme": "X-Api-Key", "in": "header",
"deviceAuthorizationUrl": "https://api.example.com/auth/device",
"tokenUrl": "https://api.example.com/auth/token"
}
The agent asks — no credential, form-encoded:
POST /auth/device
Content-Type: application/x-www-form-urlencoded
client_id=best-mcp&connection=acme-payroll&agent_label=Claude%20on%20Ada%27s%20laptop
The service answers at once:
{
"device_code": "GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/activate",
"verification_uri_complete": "https://example.com/activate?code=WDJB-MJHT",
"expires_in": 900,
"interval": 5,
"note": "Give the person this link, verification_uri_complete (or verification_uri and user_code), to open in their own browser, where they sign in and approve you. Never open it yourself or in a browser you control: approving is theirs. Then poll tokenUrl no faster than interval, with a form of grant_type=urn:ietf:params:oauth:grant-type:device_code, device_code and client_id. Its answer carries a secret credential and, in name, the connection's name: save the credential straight to your client's credential store or to a file only the person can read, under that name, and never put it in a message, a command or any output: read the rest of the answer without it."
}
The agent gives the person the link and the code before anything else, to open in their own browser — it never opens the link itself — then polls:
POST /auth/token
Content-Type: application/x-www-form-urlencoded
grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Adevice_code&device_code=GmRhmhcxhwAzkoEqiMEg_DnyEysNkuNhszIySk9eS
While the person has not acted: 400 { "error": "authorization_pending" } — not an error; wait interval seconds and ask again. slow_down means wait longer. access_denied and expired_token end the registration.
On approval:
{
"access_token": "…",
"token_type": "apikey",
"auth_header": "X-Api-Key",
"tenant_id": "acme",
"name": "acme-payroll"
}
With name, tenant_id and auth_header the agent keeps the credential under the connection's name, expands tenants.manifest, fetches the account's manifest and is at work. It never asked the person for a key or a tenant ID.
Why not a BEST command
It is tempting to model registration as a command on a public surface — request-registration, then a query to poll, then a claim. It does not work, for one reason: a command has no synchronous answer. The service cannot hand an anonymous caller anything at the moment it asks, so the only tie between the caller and its registration is an identifier the caller chose — and that identifier ends up being accepted as the secret when the credential is claimed.
Two things are wrong with that. An identifier is logged, projected and indexed by design; whoever can read those can claim the credential between approval and claim. And when the caller is a model, the "random" identifier is minted by something that cannot produce randomness: models repeat the example values they have seen. RFC 8628's first step is synchronous and its secret is generated by the service, which removes both problems. Hence the rule in the Security Requirements: identifiers are not secrets, and every secret a service relies on is generated by the service.
One backend, two kinds of person
A common shape: customers sign in on app.example.com and approve agents for their own account, while the operator's staff sign in on backoffice.example.com — another identity provider — and approve agents for the accounts they administer. The two identities never join, the customer surface never mentions the back office, and both sit on one BEST API.
BEST needs nothing new for it. A service is named by a domain (Name Resolution), so this is two names and two manifests:
- Each host serves its own
/.well-known/best.app.example.com/.well-known/bestandbackoffice.example.com/.well-known/bestare two root manifests. The backend chooses which door it is answering by the request's host, never by a path prefix: a consumer resolves a name at the host's well-known path and looks nowhere else. - Each door has its own registration. Its
deviceAuthorizationUrlandtokenUrlare on its own host, and theverification_uriit answers is that door's own approval page. A person approves where they sign in. - The door is stamped on the registration when it opens, so one host's approval page refuses a code issued by the other.
- The consumer keeps them apart by name. A credential is bound to the name it was issued under, and is only ever sent to that name's canonical host. A client holding both — a person who is also staff — holds two credentials under two names, and neither is ever used for the other.
See also
- SPEC.md — Agent Registration — the normative exchange
- Security — Identifiers are not secrets and Credentials stay out of transcripts
- Discovery — Name Resolution — how the agent got from "example.com" to the manifest in the first place