Embed "Manage API keys"
Drop one React component into your own dashboard and your customers manage their own keys, scoped to their own tenant.
@keyring/react renders a list of the signed-in customer's keys with create, rotate and revoke, and talks only to the tenant-scoped embed plane at /v1/embed/*. It holds only what your getToken() returns and refuses anything that looks like a krsk_ secret key before any network call.
1. Register your origin
Settings → Workspace settings → embed origins lists the origins your web app runs on, such as https://app.example.com, with no path. An embed token is bound to those origins and the embed plane honours a matching Origin header.
2. Mint a token on your server
Your server exchanges its secret key for a five-minute embed token scoped to one tenant. A Next.js Server Action is the shape below; any server-side function that can hold the secret key works.
export async function mintEmbedToken(): Promise<string> {
const tenantId = await currentTenantId();
const response = await fetch(
`${process.env.KEYRING_BASE_URL}/v1/embed_tokens`,
{
method: 'POST',
headers: {
authorization: `Bearer ${process.env.KEYRING_SECRET_KEY}`,
'content-type': 'application/json',
},
body: JSON.stringify({
project_id: process.env.KEYRING_PROJECT_ID,
env: 'live',
tenant_id: tenantId,
scopes: ['keys:read', 'keys:write', 'key_scope:orders:read'],
}),
},
);
if (!response.ok)
throw new Error(`Could not mint an embed token: ${response.status}`);
const body = (await response.json()) as { token: string };
return body.token;
}Scopes decide what the session may do: keys:read, keys:write, and key_scope:<name> for each scope the customer may put on a key they mint. Those decisions are made here, on your server, never in the browser.
3. Render the component
import { KeyringKeys } from '@keyring/react';
import { mintEmbedToken } from './actions';
export default function KeysPage() {
return (
<KeyringKeys
getToken={mintEmbedToken}
baseUrl={process.env.KEYRING_BASE_URL!}
/>
);
}baseUrl is required and has no default. The component never guesses a host.
Both files are compiled against the real package by the docs' test suite.
What it does
- Calls
getToken()on mount and again before the token expires, scheduled against a monotonic clock so a skewed browser clock cannot cause a refresh storm. - On any refused token, shows a Reconnect state that calls
getToken()again, and never retries with the refused token. - Shows a minted or rotated key's plaintext once, and never re-displays a dismissed one; there is no control that can reopen it.
- Renders only the controls the token's scopes grant. A
keys:readtoken renders a read-only list. - Sends one request per click: mutations are single-flight.
- Talks to nothing but the embed plane at
baseUrl. No analytics, no third-party script, no iframe.
Markup
Unstyled, with a keyring-keys__* class on every element so you style it. There is no theming system in this version, and no web-component or iframe build.
Revoking a customer's sessions
Every embed token for a tenant carries the tenant's embed epoch. Bumping it from the tenant's page in the dashboard invalidates every outstanding token at once.