Agent door

Authentication

The door authenticates an agent with an identity assertion from a trusted issuer, mints an org-scoped session whose lifetime the grant sets (one hour by default, no refresh), and — optionally — binds that session to a proof-of-possession key.

The door does not invent its own auth. It uses standards-based OAuth discovery, the ID-JAG identity-assertion authorization grant, and optional DPoP proof-of-possession. The base host is https://api.yellowhead.digital.

Discovery

Two anonymously-fetchable metadata documents describe the door:

DocumentEndpoint
Protected-resource metadata (RFC 9728) GET /.well-known/oauth-protected-resource
Authorization-server metadata (RFC 8414) GET /.well-known/oauth-authorization-server

The authorization-server document advertises the grant type, the identity-assertion profile, and the DPoP signing algorithms the token endpoint accepts:

  • Grant type: urn:ietf:params:oauth:grant-type:jwt-bearer
  • Identity-assertion profile: urn:ietf:params:oauth:grant-profile:id-jag
  • Assertion type: urn:ietf:params:oauth:token-type:id-jag
  • Token endpoint: POST /mcp/oauth/token
  • Token-endpoint client auth: none (the assertion, and optionally a DPoP proof, are the credential)
  • DPoP signing algorithms: RS256, PS256, ES256, ES384, EdDSA

ID-JAG: the identity assertion

An agent authenticates by presenting an ID-JAG — a signed JWT issued by an identity provider your workspace has registered as a trusted issuer. The token endpoint verifies the assertion end to end: its type, signing algorithm, signature (against the issuer's published keys), audience, expiry, and required claims, and that the asserted subject and client are permitted by a live grant in your workspace.

A single assertion mints a single session — its unique identifier is consumed on use, so an assertion cannot be replayed. A new session needs a fresh assertion. Where more than one grant could match an assertion, the client selects one by passing a grant_id.

Request

POST /mcp/oauth/token
Content-Type: application/x-www-form-urlencoded

grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer
&assertion=<the ID-JAG>
&grant_id=<optional grant selector>

Response

{
  "access_token": "yh_mcp_...",
  "token_type": "Bearer",       // "DPoP" when the session is key-bound
  "expires_in": 3600,
  "scope": "diagnostic.read report.read ..."   // the grant's capabilities
}

The access token is an opaque session token (prefix yh_mcp_), not a JWT. Every request re-checks the session row and the grant behind it, so revoking a grant or a session takes effect within about a minute — see below.

Calling the door

Present the session token as a bearer credential on every MCP transport request:

POST /mcp
Authorization: Bearer yh_mcp_...
Content-Type: application/json
MCP-Protocol-Version: 2025-06-18

{ "jsonrpc": "2.0", "method": "tools/list", "id": 1 }

POST /mcp carries the JSON-RPC calls (initialize, tools/list, tools/call). DELETE /mcp ends a protocol session. Tool results return as JSON in the POST response body; the door does not open a server-sent event stream.

DPoP: sender-constraining (optional, sometimes required)

A bearer session token is, on its own, a bearer credential: whoever holds it can use it. DPoP (RFC 9449) closes that gap by binding the session to a key the client proves it holds. When a client presents a valid DPoP proof at the token endpoint, the session is bound to that key's thumbprint (a binding that can never be changed afterward) and the response returns token_type: DPoP. A key-bound session must then present a fresh proof — signed by the same key, over this exact request and this exact token — on every transport request.

DPoP is optional by default and required per grant: a workspace can mark a grant so that only an issuer-confirmed, key-bound session is admitted — which is the posture for every external-customer grant. DPoP is also the prerequisite for the direct-apply write path (see Write approvals): without a proven key binding, writes are always held for a human.

Sessions, revocation, and expiry

  • Lifetime. A session lives until expires_in elapses (the grant's configured TTL, default one hour) or it is terminated early.
  • No refresh. There is no refresh token. A new session needs a fresh identity assertion.
  • Revocation lands within ~60 seconds. Revoking the grant, revoking the trusted issuer (which kills every session under it), or narrowing the grant's allowed subjects terminates a live session on its next revalidation. Deleting the session row is immediate.
  • Redeploys. A backend redeploy clears in-memory protocol sessions; the bearer session survives in the database, so the client re-runs initialize with the same token and continues.