Skip to Content
Integrate Agent SSO
View .md

Service Integration

This guide shows how to add AI agent authentication to any web service. After integration, agents with an Alien Agent ID can authenticate using cryptographically signed tokens — no API keys, no shared secrets, no pre-registration.

How It Works

The token is self-contained: it carries the agent’s public key and the full owner proof chain (owner binding + id_token), so your service can verify both the agent’s identity and the owner claim with a single JWKS fetch.

Token Format

Agents send authentication via the Authorization header:

Authorization: AgentID <base64url-encoded-json>

Decoded token payload:

{ "v": 1, "fingerprint": "f5d9fac49457e9e359078815f7c1c568a56207a4a5c0b05a11ce3cf54bc8d4f8", "publicKeyPem": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA...\n-----END PUBLIC KEY-----\n", "owner": "00000003010000000000539c741e0df8", "timestamp": 1774531517000, "nonce": "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6", "sig": "<Ed25519-base64url-signature>", "ownerBinding": { "payload": {}, "payloadHash": "...", "signature": "..." }, "idToken": "<RS256 JWT from Alien SSO>" }
FieldTypeDescription
vnumberToken version (always 1)
fingerprintstringSHA-256 hash of the agent’s public key DER encoding (64 hex chars)
publicKeyPemstringAgent’s Ed25519 public key in SPKI PEM format
ownerstring | nullAlienID address of the human who authorized this agent
timestampnumberUnix timestamp in milliseconds
noncestringRandom 128-bit hex string (replay resistance)
sigstringEd25519 signature (base64url) over canonical JSON of core fields
ownerBindingobjectOwner binding record for full chain verification
idTokenstringRS256 JWT from Alien SSO for owner verification

Signature Computation

The signature is computed over canonical JSON of the core payload fields only (keys sorted alphabetically, no whitespace). The ownerBinding and idToken fields are appended after signing and are not covered by sig:

canonical = JSON.stringify(sortKeysRecursively({ v, fingerprint, publicKeyPem, owner, timestamp, nonce })) sig = Ed25519.sign(canonical, agentPrivateKey)

Using the SDK (Node.js)

Install the SDK package. Zero dependencies — uses only Node.js built-in crypto module.

npm install @alien-id/sso-agent-id

Basic Verification

Verifies the agent holds the private key. The owner field is not cryptographically verified — use verifyAgentTokenWithOwner for that.

import { verifyAgentRequest } from "@alien-id/sso-agent-id"; function authenticateAgent(req) { return verifyAgentRequest(req); }

Owner Verification

Verifies the full chain: agent key → owner binding → id_token → Alien SSO JWKS. This proves the owner claim is backed by the SSO server.

import { fetchAlienJWKS, verifyAgentRequestWithOwner, } from "@alien-id/sso-agent-id"; // Fetch JWKS at startup and cache it (refresh periodically) const jwks = await fetchAlienJWKS(); function authenticateAgent(req) { return verifyAgentRequestWithOwner(req, { jwks }); }

verifyAgentToken(tokenB64, opts?) API

Parameters:

  • tokenB64 (string) — Base64url-encoded token from the Authorization header
  • opts.maxAgeMs (number, optional) — Maximum token age in milliseconds. Default: 300000 (5 minutes)
  • opts.clockSkewMs (number, optional) — Allowed clock skew for future-dated tokens. Default: 30000 (30 seconds)

Returns on success:

{ ok: true, fingerprint: "f5d9fac4...", // Agent identity (stable across sessions) publicKeyPem: "-----BEGIN...", // Agent's Ed25519 public key owner: "0000000301...", // Human owner's AlienID address (or null) ownerVerified: false, // Use verifyAgentTokenWithOwner for true timestamp: 1774531517000, // When the token was created nonce: "a1b2c3d4..." // Unique per token }

verifyAgentTokenWithOwner(tokenB64, opts) API

Parameters:

  • tokenB64 (string) — Base64url-encoded token
  • opts.jwks (JWKS) — Pre-fetched JWKS from fetchAlienJWKS()
  • opts.maxAgeMs (number, optional) — Maximum token age. Default: 300000
  • opts.clockSkewMs (number, optional) — Allowed clock skew. Default: 30000

Returns on success:

{ ok: true, fingerprint: "f5d9fac4...", publicKeyPem: "-----BEGIN...", owner: "0000000301...", ownerVerified: true, // Owner cryptographically verified ownerProofVerified: false, // true if human's session proof was present and valid issuer: "https://sso.alien-api.com", timestamp: 1774531517000, nonce: "a1b2c3d4..." }

Returns on failure:

{ ok: false, error: "Token expired (age: 312s)" }

Possible errors:

ErrorMeaning
Invalid token encodingToken is not valid base64url JSON
Unsupported token version: NUnknown token version
Token expired (age: Ns)Token is older than maxAgeMs
Invalid public key in tokenpublicKeyPem is not a valid Ed25519 key
Fingerprint does not match public keyfingerprint doesn’t match SHA-256(publicKeyDER)
Signature verification failedEd25519 signature is invalid — token was tampered
Missing field: ownerBindingToken lacks owner binding (needed for owner verification)
Missing field: idTokenToken lacks id_token (needed for owner verification)
Owner binding signature verification failedBinding was not signed by this agent’s key
Owner binding agent fingerprint mismatchBinding references a different agent key
id_token hash does not match owner bindingid_token doesn’t match the binding
id_token signature verification failedRS256 signature invalid against JWKS
id_token sub does not match token ownerid_token subject differs from claimed owner

Implement Verification Yourself

The verification algorithm is straightforward to implement in any language with Ed25519 and SHA-256 support.

Step-by-Step Verification

1. DECODE raw = base64url_decode(token) parsed = JSON.parse(raw) 2. CHECK VERSION assert parsed.v == 1 3. CHECK TIMESTAMP age = now_ms() - parsed.timestamp assert age >= 0 AND age <= 300000 # 5 minutes 4. VERIFY FINGERPRINT der = parse_spki_pem(parsed.publicKeyPem) computed = hex(sha256(der)) assert computed == parsed.fingerprint 5. VERIFY SIGNATURE payload = { v, fingerprint, publicKeyPem, owner, timestamp, nonce } canonical = canonical_json(payload) # sorted keys, no whitespace sig_bytes = base64url_decode(parsed.sig) pubkey = parse_ed25519_public_key(parsed.publicKeyPem) assert ed25519_verify(canonical, sig_bytes, pubkey)

Access Control Patterns

Allow Any Verified Agent

Accept any agent with a valid token:

function requireAgent(req, res, next) { const result = verifyAgent(req); if (!result.ok) return res.status(401).json({ error: result.error }); req.agent = result; next(); }

Require Human-Owned Agents

Reject agents that don’t have a human owner:

function requireOwnedAgent(req, res, next) { const result = verifyAgent(req); if (!result.ok) return res.status(401).json({ error: result.error }); if (!result.owner) return res.status(403).json({ error: "Human-owned agent required" }); req.agent = result; next(); }

Allow-List by Agent Fingerprint

Pre-register known agent fingerprints:

const ALLOWED_AGENTS = new Set([ "f5d9fac49457e9e359078815f7c1c568a56207a4a5c0b05a11ce3cf54bc8d4f8", "42fbde2a3f7ca6dfdc61fc74e54227c84ff0a6e85f1a838052d9aa60ca2b527f", ]); function requireKnownAgent(req, res, next) { const result = verifyAgent(req); if (!result.ok) return res.status(401).json({ error: result.error }); if (!ALLOWED_AGENTS.has(result.fingerprint)) { return res.status(403).json({ error: "Agent not authorized" }); } req.agent = result; next(); }

Allow-List by Owner (Verified)

Trust any agent owned by specific humans. Uses verifyAgentRequestWithOwner to cryptographically verify the owner claim — without this, any agent could claim any owner address.

import { fetchAlienJWKS, verifyAgentRequestWithOwner, } from "@alien-id/sso-agent-id"; const jwks = await fetchAlienJWKS(); const ALLOWED_OWNERS = new Set([ "00000003010000000000539c741e0df8", // Alice "00000003010000000000542b891a3c47", // Bob ]); function requireAuthorizedOwner(req, res, next) { const result = verifyAgentRequestWithOwner(req, { jwks }); if (!result.ok) return res.status(401).json({ error: result.error }); if (!result.owner || !ALLOWED_OWNERS.has(result.owner)) { return res.status(403).json({ error: "Agent owner not authorized" }); } req.agent = result; next(); }

Rate Limiting by Agent

const rateLimits = new Map(); function rateLimit(maxRequests, windowMs) { return (req, res, next) => { const fp = req.agent.fingerprint; const now = Date.now(); const entry = rateLimits.get(fp) || { count: 0, windowStart: now }; if (now - entry.windowStart > windowMs) { entry.count = 0; entry.windowStart = now; } entry.count++; rateLimits.set(fp, entry); if (entry.count > maxRequests) { return res.status(429).json({ error: "Rate limit exceeded" }); } next(); }; } // 100 requests per minute per agent app.use("/api", requireAgent, rateLimit(100, 60000));

Owner Verification

Basic token verification (verifyAgentToken) confirms the agent holds the private key but does not verify the owner field. Any process can generate a keypair and claim any owner address.

Full owner verification (verifyAgentTokenWithOwner) proves the complete chain:

Verification Steps (Performed by the SDK)

  1. Agent key verification — Ed25519 signature valid, fingerprint matches public key, token is fresh
  2. Owner binding signature — binding was signed by the same agent key (Ed25519)
  3. Binding → agent keyagentInstance.publicKeyFingerprint matches token fingerprint
  4. Binding → ownerownerSessionSub matches token owner
  5. Binding → id_tokenSHA-256(idToken) matches idTokenHash in binding
  6. id_token signature — RS256 signature valid against Alien SSO JWKS
  7. id_token → ownersub claim matches token owner
  8. Owner session proof (optional) — human’s Ed25519 consent signature, if present

When to Use Owner Verification

ScenarioverifyAgentTokenverifyAgentTokenWithOwner
Read-only API accessSufficientNot needed
Write operationsSufficientRecommended
Financial transactionsNot sufficientRequired
Audit-sensitive operationsSufficientRecommended
First-time agent registrationNot sufficientRequired
Owner-based access controlNot sufficientRequired

Security Considerations

What Basic Verification Proves (verifyAgentToken)

  • The agent holds the Ed25519 private key at the time of signing
  • The token was created within the last 5 minutes (configurable)
  • The owner field is present but not verified (ownerVerified: false)

What Owner Verification Proves (verifyAgentTokenWithOwner)

All of the above, plus:

  • The owner claim is cryptographically verified against Alien SSO (ownerVerified: true)
  • The agent key was bound to the owner via a signed owner binding
  • The Alien SSO server attested the binding via an RS256-signed id_token
  • If ownerProofVerified: true, the human explicitly approved this agent via the Alien App

Replay Protection

  • Tokens include a random nonce and a timestamp
  • The 5-minute expiry window limits replay
  • For stricter protection, track seen nonces per agent fingerprint

Clock Skew

  • Tokens use the agent’s local clock
  • The default 5-minute window accommodates reasonable clock drift
  • Adjust maxAgeMs for known clock skew issues
  • Reject tokens with negative age (timestamp in the future)

Transport Security

  • Always use HTTPS in production
  • The token is a bearer credential — capture enables replay within validity window

Enabling Agent Access with ALIEN-SKILL.md

To let AI agents discover and authenticate to your service, host an ALIEN-SKILL.md file at a public URL (default: /ALIEN-SKILL.md). This is a markdown file written for the agent, not for humans — the agent reads it and follows the instructions autonomously.

What to Include

Your ALIEN-SKILL.md should contain:

  1. What the service does — a one-line description the agent can use to decide if it’s relevant
  2. Base URL — tell the agent to use the same origin it fetched the ALIEN-SKILL.md from
  3. Prerequisites — link to the Agent ID skill install command (npx skills add alien-id/agent-id)
  4. How to authenticate — show the auth-header command and the Authorization: AgentID <token> format
  5. API reference — list your endpoints, methods, required auth, and request/response formats
  6. Examples — curl commands the agent can adapt

Example

# My Service — Agent Authentication > You are an AI agent. This service accepts Alien Agent ID tokens. > > **Base URL**: Use the same origin you fetched this ALIEN-SKILL.md from. ## Prerequisites You need an Alien Agent ID. If you don't have one: \`\`\`bash npx skills add alien-id/agent-id \`\`\` Then follow the bootstrap instructions to get your identity. ## Authenticate \`\`\`bash AUTH=$(node /path/to/cli.mjs auth-header --raw) \`\`\` ## API Reference | Endpoint | Method | Auth | Description | | ----------- | ------ | ------- | ----------- | | `/api/data` | GET | AgentID | Fetch data | | `/api/data` | POST | AgentID | Submit data | ## Auth Header Format \`\`\`text Authorization: AgentID <base64url-encoded-token> \`\`\` Tokens are valid for 5 minutes. Generate a fresh one for each request.

Hosting

  • Next.js: put it in public/ALIEN-SKILL.md — served as a static file
  • Express: use app.use(express.static('public')) with the file in public/
  • Any server: serve it at any stable URL and share that URL with agents

How Agents Find It

Agents discover your ALIEN-SKILL.md when a human pastes your service URL into the agent’s chat, or when the URL is referenced in the sign-in modal via the @alien-id/sso-react SDK (default skillUrl: "/ALIEN-SKILL.md"). The agent fetches the file, reads the instructions, and authenticates automatically.

If your app uses the SSO React SDK, you can enable the agent tab in the sign-in modal to surface the install command and your service URL directly to users. See SSO React API Reference — Agent Mode for setup.

For a working example, see the Demo App which includes a complete ALIEN-SKILL.md.

Last updated on