Agent Consent Flow
Agent Consent Flow is a hosted private beta for signed, scoped authorization requests around independently conformable AIP Consent. It gives developers a real authorization flow without defining a new standard, changing AIP, or claiming OAuth compatibility.
The hosted service validates a developer's signed request, shows the requested scopes to an authenticated user, records the decision, and issues a short-lived opaque token through a server-side code exchange. The user's underlying service credentials are never returned by the Consent protocol.
Availability
Agent Consent Flow is available to signed-in Gazebo users as a private beta in the Agent Consent Flow area of the app. The TypeScript SDK currently ships with the hosted beta source and is not yet published to a public package registry.
Protocol flow
- Generate an ES256 P-256 key pair. Keep the private JWK in your server-side secrets.
- Register the public JWK, exact callback URI, and allowed scopes in Gazebo.
- Generate a random callback state and an S256 PKCE verifier/challenge pair.
- Sign a short-lived authorization request JWT with your private JWK.
- Submit the JWT to
POST /api/consent/v1/authorization. - Send the user to the returned transaction at
https://app.gazebohq.com/consent/authorize?transaction=.... - Validate the state returned to your exact callback URI.
- Exchange the one-time code and PKCE verifier at
POST /api/consent/v1/token. - Introspect the opaque token before honoring its scopes.
- Revoke the token or the complete user grant when access is no longer required.
SDK example
import {
createSignedAuthorizationRequest,
exchangeAuthorizationCode,
generateEs256KeyPair,
generatePkce,
generateState,
validateCallbackState,
} from "@workspace/consent-sdk";
// Run once during setup. Register publicKeyJwk in Gazebo and keep
// privateKeyJwk only in your server-side secret store.
const { publicKeyJwk, privateKeyJwk } = generateEs256KeyPair();
// Run for each authorization attempt.
const pkce = generatePkce();
const state = generateState();
const request = createSignedAuthorizationRequest(
{
clientId: process.env.GAZEBO_CONSENT_CLIENT_ID!,
audience: "gazebo-consent-v1",
redirectUri: "https://agent.example.com/consent/callback",
scopes: ["issues.read", "issues.create"],
codeChallenge: pkce.challenge,
state,
},
privateKeyJwk,
);
const authorization = await fetch(
"https://gazebohq.com/api/consent/v1/authorization",
{
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ requestJwt: request.requestJwt }),
},
).then((response) => response.json());
const consentUrl =
`https://app.gazebohq.com/consent/authorize?transaction=${authorization.transactionId}`;
// At your registered callback:
if (!validateCallbackState(callbackState, state)) {
throw new Error("Invalid callback state");
}
const token = await exchangeAuthorizationCode("https://gazebohq.com", {
clientId: process.env.GAZEBO_CONSENT_CLIENT_ID!,
code: callbackCode,
redirectUri: "https://agent.example.com/consent/callback",
codeVerifier: pkce.verifier,
});
Persist the state and PKCE verifier in a short-lived, server-side session tied to the browser that started the request. Never place the private JWK, verifier, authorization code, or access token in logs.
Signed request requirements
Authorization request JWTs must:
- use
ES256with a registered P-256 public JWK; - use the registered client ID as
iss; - use
gazebo-consent-v1asaud; - expire within five minutes;
- contain a unique
jti; - use an exact registered HTTPS callback URI (
localhostis allowed for local development); - request only registered scopes;
- include an S256 PKCE challenge.
Wildcards, callback fragments, unsigned requests, alternate signing algorithms, plain PKCE, replayed request IDs, and scope expansion are rejected.
Approval and denial
The user must have an authenticated Gazebo browser session to approve or deny a transaction. API keys, agent tokens, and Consent access tokens cannot make consent decisions.
Approval creates a scoped grant and a one-time authorization code in one database transaction. Denial creates no grant and returns error=access_denied with the original state to the registered callback.
Token handling
- Authorization codes expire after one minute and can be consumed once.
- Access tokens are opaque and expire after one hour.
- Only SHA-256 hashes of codes and tokens are stored.
- Token introspection returns only active status, client ID, scopes, and expiry.
- Deactivating a client or revoking a grant immediately revokes its active access tokens.
- Expanding scope always requires a new signed request and user decision.
The developer's resource server remains responsible for mapping each scope to a real operation and denying any operation not covered by an active introspection result.
Security boundary
Agent Consent Flow authorizes scopes; it does not send a user's underlying vault credentials to the client. A client cannot use a Consent token as a Gazebo account token, agent token, or MCP token, and Consent tokens are rejected outside the isolated Consent protocol/resource-server integration boundary.
The current private beta intentionally uses short-lived opaque tokens with database-backed revocation rather than self-contained bearer JWTs. This makes revocation immediate and keeps the authorization server authoritative.
Relationship to AIP
AIP Consent is normative and remains unchanged. Agent Consent Flow is one implementation around that independently conformable section. Other implementations can conform directly to AIP without using Agent Consent Flow.