Checkpoint Docs
Govern (KYA-OS)

Consent Flows

Implement user consent for KYA-OS agent actions

Overview

When an AI agent requests access to a user's resources via OAuth or another authentication method, the user sees a consent page before authorization is granted. Checkpoint provides a customizable consent flow that clearly communicates what the agent is requesting and gives the user full control over what to approve.

The consent UI is built with @kya-os/consent — a library of Web Components powered by Lit. Because it uses the Web Components standard, the library renders natively in any framework or no framework at all — React, Next.js, Vue, Angular, Svelte, or vanilla JavaScript.

Built on Web Components — renders natively in any framework

React
Next.js
Vue
Angular
Svelte
Vanilla JS

Prerequisites

  • A Checkpoint project with at least one authentication method configured. See Credentials for how to find your Project ID and API key in Installations.
  • To embed or preview the @kya-os/consent components in your own app (rather than just customizing the hosted page), a Node/npm project to install the package into.

Live Preview

Switch between authentication modes to see how each consent screen appears to users:

Permission Request

Allow Shopping Assistant to access your account

This agent is requesting the following permissions:

This permission will expire in 7 days

These are live previews of the consent screens users see when authorizing an agent.

1. Agent initiates authorization request
2. User is redirected to the consent page
3. Consent page displays:
   - Agent identity (DID and display name)
   - Requested scopes in human-readable form
   - Your application branding
   - Authentication method (OAuth button, login form, or simple approve)
4. User authenticates (if required) and approves or denies
5. If approved → delegation is created, agent receives authorization
6. If denied → agent receives an error, no delegation is created

The consent page is hosted by Checkpoint and shows:

  • Your application name and logo (configured in dashboard)
  • Agent identity — The requesting agent's DID and display name
  • Requested permissions — Human-readable descriptions of each scope
  • Authentication UI — Varies by auth method (OAuth button, credentials form, or approve/deny)

Users can review exactly what they're granting before making a decision.

Via the Dashboard

  1. Navigate to Policy → Auth (/dashboard/{orgId}/{projectId}/policy/auth) — the consent editor lives here
  2. Configure:
    • Application display name and logo
    • Custom description text
    • Scope descriptions (human-readable wording for each permission)
    • Primary theme color
  3. Save — the hosted consent page picks up your changes

A few settings have not moved yet: Terms of Service / Privacy Policy URLs and the secondary brand color are still edited on the legacy Control Access surface (/dashboard/{orgId}/{projectId} /control-access/consent).

Via the API

The consent configuration is read-only over the API — GET returns the current config, and there is no write endpoint (a PUT returns 405). Edits happen in the dashboard consent editor (Policy → Auth).

curl -X GET https://kya.vouched.id/api/v1/bouncer/projects/{projectId}/consent-config \
  -H "X-API-Key: $AGENTSHIELD_API_KEY" \
  -H "X-Project-Id: {projectId}"

The response data contains the config object (branding, terms, UI settings, custom fields) plus metadata (version, updatedAt, cacheVersion) — useful for KYA-OS servers that render the consent screen themselves.

Public Configuration Endpoint

The consent page fetches configuration from a public endpoint (no API key required):

curl -X GET https://kya.vouched.id/api/public/consent-config/{projectId}

Scope Descriptions

Map technical scope identifiers to user-friendly descriptions. This helps users understand what they're approving:

ScopeDescription Shown to User
files:read"View your files and documents"
files:write"Create, edit, and upload files"
cart:write"Add and remove items from your cart"
payment:process"Complete purchases on your behalf"

Scopes without configured descriptions will display the raw scope identifier (e.g., files:write) to the user. Always provide human-readable descriptions for a better user experience.

Capabilities

Beyond raw scope descriptions, a project's consentConfig can define a richer, humanized capability layout: named, described permission rows grouped into sections, each with an icon, a risk level, and the Cedar policy fragment bound to the issued delegation when the user approves it. When capabilities is set, the hosted consent page renders this layout instead of the raw scope checkboxes above. Capabilities are saved as part of your project's Bouncer configuration, validated by the same schema as the rest of consentConfig.

A capability group:

FieldTypeDescription
idstringLowercase group id (a-z0-9._-)
labelstringSection heading shown on the consent screen
capabilitiesCapability array1 to 20 capabilities in this group

A capability:

FieldTypeDescription
idstringLowercase capability id (a-z0-9._-)
labelstringName shown to the user (max 80 chars)
descriptionstringWhat the agent can do (max 280 chars)
icon'search' | 'cart' | 'card' | 'pin' | 'pin-new' | 'shield' | 'key' | 'tools' | 'user' | 'calendar' | 'lock' | 'eye' | 'send' | 'package' | 'neutral'Icon shown next to the capability
riskLevel'low' | 'medium' | 'high' | 'critical'How sensitive this grant is
defaultOnbooleanWhether the checkbox starts checked
cedarstringCedar policy fragment (permit (...) or forbid (...)) bound at issuance
scopesstring array1 to 20 underlying scope identifiers this capability grants
categorystring, optionalFreeform grouping tag

The riskLevel / defaultOn floor

A high or critical capability can never be saved with defaultOn: true. This is a hard floor enforced when the configuration is saved, not a client-side check: a checkbox for a grant that requires step-up trust cannot ship pre-checked, regardless of how the capability is otherwise configured.

If a saved capability violates this, the write is rejected before anything reaches storage, with a 400 response shaped like:

{
  "success": false,
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid Bouncer configuration",
    "details": [
      {
        "message": "Capability \"payment-charge\" has riskLevel \"critical\" and cannot default on. High/critical capabilities must be structurally unmintable at first contact (First-Contact Scope Cap section 3, Rule 1)."
      }
    ]
  }
}

The message names the offending capability's id directly, so a save with many capabilities points at the exact row to fix. Resolve it by either lowering riskLevel or setting defaultOn: false, then save again.

This floor applies to every save through this endpoint, not just an edit to the capability itself. A stored config that already violates it (from before this floor existed) will also reject an unrelated resave that round-trips it unchanged — including the dashboard's Cache Refresh action, which resubmits the whole stored config to bump a cache-busting timestamp. Fix the violating capability first; Cache Refresh will succeed again once it's resolved.

Checkpoint manages consent state automatically:

  • Pending — User has been shown the consent page but hasn't responded
  • Approved — User approved, delegation created
  • Denied — User denied, no delegation created
  • Expired — Consent page timed out without a response

Consent is remembered automatically. When an agent returns with an active, unexpired delegation for the same project (and user), Checkpoint resolves and reuses that delegation instead of showing the consent page again. Revoke the delegation to force a fresh consent flow.

Redirect Handling

After the user approves or denies consent:

  • Approved: Redirects to the agent's redirect_uri with an authorization code
  • Denied: Redirects to the agent's redirect_uri with an error=access_denied parameter
// Approved
https://agent.example.com/callback?code=auth_xyz&state=random_state

// Denied
https://agent.example.com/callback?error=access_denied&state=random_state

The consent UI is built with @kya-os/consent, a Web Component library powered by Lit. React wrappers are available via @kya-os/consent/react, and the raw custom elements (<mcp-consent>, <consent-shell>, etc.) work in any framework — Vue, Angular, Svelte, or plain HTML. You can use these components to embed consent experiences in your own applications or to preview the flow during development.

Install the package before using any of the components below:

npm install @kya-os/consent

Available Components

ComponentPurpose
McpConsentReactFull consent flow (composite — includes all below)
ConsentShellReactContainer with branding (logo, colors, title)
ConsentPermissionsReactScope/permission list with optional checkboxes
ConsentButtonReactStyled action button (Allow / Deny)
ConsentCheckboxReactCheckbox with label (terms acceptance)
ConsentInputReactText input (username, email, password fields)
ConsentTermsReactTerms & privacy policy checkbox with links
ConsentOAuthButtonReactOAuth provider sign-in button

The McpConsentReact component renders the complete consent flow for any authentication method:

import { McpConsentReact } from '@kya-os/consent/react';

<McpConsentReact
  config={{
    branding: {
      primaryColor: '#2563eb',
      logo: { url: 'https://example.com/logo.png', alt: 'My App' },
    },
    ui: {
      title: 'Grant Access',
      description: 'Allow this agent to access your account',
    },
    terms: {
      termsText: 'Terms of Service',
      termsUrl: 'https://example.com/terms',
      privacyText: 'Privacy Policy',
      privacyUrl: 'https://example.com/privacy',
    },
  }}
  tool="checkout"
  scopes={['cart:read', 'cart:write', 'payment:process']}
  agentDid="did:key:z6Mk..."
  agentName="Shopping Assistant"
  onApprove={(e) => console.log('Approved:', e.detail)}
  onDeny={() => console.log('Denied')}
/>;

Compose individual components for a custom layout:

import {
  ConsentShellReact,
  ConsentPermissionsReact,
  ConsentTermsReact,
  ConsentButtonReact,
} from '@kya-os/consent/react';

<ConsentShellReact pageTitle="Permission Request" companyName="Acme Corp" primaryColor="#1e2d57">
  <div slot="content">
    <ConsentPermissionsReact
      scopes={[
        { id: 'files:read', label: 'Read your files', required: true },
        { id: 'files:write', label: 'Create and edit files' },
      ]}
      interactive
      selectAll
      iconStyle="shield"
    />
    <ConsentTermsReact
      text="Terms of Service"
      url="https://example.com/terms"
      privacyText="Privacy Policy"
      privacyUrl="https://example.com/privacy"
      required
    />
  </div>
  <div slot="footer">
    <ConsentButtonReact variant="secondary">Cancel</ConsentButtonReact>
    <ConsentButtonReact variant="primary">Allow Access</ConsentButtonReact>
  </div>
</ConsentShellReact>;

CSS Theming

The consent components respect CSS custom properties for consistent branding:

:root {
  --consent-primary: #2563eb; /* Primary brand color */
  --consent-secondary: #dbeafe; /* Secondary/accent color */
}

These are automatically set when you configure primaryColor and secondaryColor on ConsentShellReact or in the config.branding object.

During development, you can preview the consent screens from the dashboard:

  1. Open Policy → Auth (/dashboard/{orgId}/{projectId}/policy/auth) — the consent and success screens are previewed inline as you edit
  2. Test with different scope combinations
  3. Switch between authentication methods to preview each mode

The full connect-page preview (the hosted page exactly as your users see it) still lives on the legacy Control Access surface: /dashboard/{orgId}/{projectId}/control-access/consent?tab=connect.

The consent page preview uses sample data. In production, the actual agent DID and requested scopes are displayed.

Testing with Components

With @kya-os/consent installed (see Consent Components), import the React wrappers and render with test data:

import { McpConsentReact } from '@kya-os/consent/react';

// Use previewStep to render specific stages
<McpConsentReact
  previewStep="consent"
  config={yourConfig}
  scopes={['files:read', 'files:write']}
  agentName="Test Agent"
/>;

Next Steps