Auth Skill
Authentication and authorization for agents using the Robutler Platform. Establishes a unified AuthContext and a secure, interoperable mechanism for agent‑to‑agent authorization via RS256 owner assertions (JWT).
Features
- API key authentication with the Robutler Platform.
- Role‑based access control: admin, owner, user.
on_connectionauthentication hook.- Agent owner scope detection (from agent metadata).
- Harmonized
AuthContextwith minimal, stable fields. - Optional agent‑to‑agent assertions via
X-Owner-Assertion(short‑lived RS256 JWT, verified via JWKS).
Configuration
import { BaseAgent } from 'webagents';
import { AuthSkill } from 'webagents/skills/auth';
const agent = new BaseAgent({
name: 'secure-agent',
model: 'openai/gpt-4o',
skills: [
new AuthSkill({
apiKey: process.env.ROBUTLER_API_KEY,
platformApiUrl: 'https://robutler.ai',
requireAuth: true,
}),
],
});platform_api_url / platformApiUrl resolves in this order: $ROBUTLER_INTERNAL_API_URL → $ROBUTLER_API_URL → http://localhost:3000.
Scopes
- admin: Platform administrators.
- owner: Agent owner (API key belongs to the agent owner).
- user: Regular authenticated users.
If not explicitly set, the default scope is user.
Identity and Context
The auth skill validates the API key during on_connection and exposes an AuthContext on the request context:
import { getContext } from 'webagents';
const context = getContext();
const auth = context.auth;
const userId = auth.userId; // overridden by JWT `sub` when verified
const agentId = auth.agentId; // from verified assertion (if provided)
const scope = auth.scope; // 'admin' | 'owner' | 'user'
const authenticated = auth.authenticated;
const assertion = auth.assertion; // decoded claims (if provided)Deprecated identity fields (e.g., origin_user_id, peer_user_id, agent_owner_user_id) have been removed in favor of the harmonized fields above.
Authentication Flow
- Extract API key from
Authorization(Bearer) orX-API-Key. - Validate API key with the Robutler Platform.
- Determine scope based on the validated user and agent ownership.
- Optionally verify
X-Owner-Assertion(RS256, JWKS) and merge acting identity intoAuthContext. - Populate
context.authwith anAuthContextinstance.
Agent‑to‑Agent Assertions (Owner Assertions)
- Primary purpose: secure, interoperable agent‑to‑agent authentication and authorization across services.
- Also enables owner‑only actions (e.g., ControlSkill) without exposing agent API keys to clients.
- Transport: send
X-Owner-Assertion: <jwt>alongside yourAuthorizationheader.
Claims
aud = robutler-agent:<agentId>— audience bound to the target agent.agent_id = <agentId>— agent identity binding.sub = <userId>— acting end‑user identity.owner_user_id = <ownerId>— agent owner (advisory).jti— unique token id for optional replay tracking.iat/nbf/exp— very short TTL (2–5 minutes).
Verification by AuthSkill
- Signature verification via JWKS (RS256).
- Enforce audience and agent binding:
aud == robutler-agent:<agentId>andagent_id == agent.id. - On success, update context:
context.auth.user_id = sub(acting identity).context.auth.agent_id = agent_id.context.auth.assertion = <decoded claims>.
- OWNER scope is derived by comparing the API‑key owner to the agent's
owner_user_id; the assertion does not grant owner scope by itself.
JWKS and configuration
- The skill discovers the JWKS at
OWNER_ASSERTION_JWKS_URLif set; otherwise at{platform_api_url}/api/auth/jwks. - Only RS256 is supported. HS256 and shared‑secret fallbacks are not supported.
[!NOTE] Owner-assertion issuance (signing JWTs, OIDC discovery, self-issued key publishing) is currently Python-only. Both SDKs perform JWKS-based verification. Track parity at internal/python-typescript-parity.md.
High‑level flow
Defaults and edge cases
- If the skill is enabled and authentication succeeds,
auth.scopedefaults touserunless elevated toowneroradmin. - If the skill is disabled (
requireAuth=false/require_auth=False) or not configured, downstream tools should treat the request as unauthenticated and avoid owner/admin‑scoped operations.
Example: protecting an owner‑only tool
import { AuthScope } from 'webagents';
import type { Context } from 'webagents';
export function updateAgentSettings(context: Context, patch: unknown): void {
if (context.auth?.scope !== AuthScope.OWNER) {
throw new Error('Owner scope required');
}
// proceed with update
}Implementation: see typescript/src/skills/auth/skill.ts and python/webagents/agents/skills/robutler/auth/skill.py.