auth-agents
v0.4.3
Published
Verify AI agent identities with Agent Auth. DID-based authentication using Ed25519 and Verifiable Credentials.
Maintainers
Readme
auth-agents
Verify AI agent identities with Agent Auth. DID-based authentication using Ed25519 and Verifiable Credentials.
Install
npm install auth-agentsQuick Start — Verify a Credential
import { AuthAgents } from "auth-agents"
const client = new AuthAgents()
const result = await client.verify("eyJhbGciOiJFZERTQSJ9...")
if (result.valid) {
console.log(result.did) // "did:key:z6Mk..."
console.log(result.agent_name) // "Claude"
console.log(result.agent_model) // "claude-opus-4-6"
console.log(result.agent_provider) // "Anthropic"
console.log(result.agent_purpose) // "Research assistant"
console.log(result.key_origin) // "server_generated" | "client_provided"
console.log(result.expires_at) // "2026-02-27T01:58:15.000Z"
}Identity Registration Flows
Agent Auth supports two registration paths. Both produce the same DID, credential, and challenge-response authentication. The difference is who holds the private key.
Flow A — Server-Generated Keys (zero friction)
The server generates an Ed25519 key pair. You receive the private key once and must store it securely. key_origin will be "server_generated".
import { AuthAgents } from "auth-agents"
const client = new AuthAgents()
// 1. Register — server generates keys
const identity = await client.register({
agent_name: "Claude",
agent_model: "claude-opus-4-6",
agent_provider: "Anthropic",
agent_purpose: "Research assistant",
})
// identity.did — "did:key:z6Mk..."
// identity.credential — VC-JWT (present to websites)
// identity.key_origin — "server_generated"
// identity.private_key_jwk — SAVE THIS. Server does not keep it.
// 2. Request a challenge
const challenge = await client.challenge(identity.did)
// 3. Sign the nonce with the private key
const signature = await AuthAgents.signChallenge(
identity.private_key_jwk!,
challenge.nonce
)
// 4. Authenticate and receive a fresh credential
const auth = await client.authenticate({
challenge_id: challenge.challenge_id,
did: identity.did,
signature,
})
if (auth.valid) {
console.log(auth.credential) // fresh VC-JWT
console.log(auth.session_token) // session ID
}Flow B — Bring Your Own Key (BYOK)
Generate the key pair locally. The private key never leaves your process. key_origin will be "client_provided".
import { AuthAgents } from "auth-agents"
const client = new AuthAgents()
// 1. Generate an Ed25519 key pair locally
const keyPair = await AuthAgents.generateKeyPair()
// keyPair.publicKeyJwk — { kty, crv, x }
// keyPair.privateKeyJwk — { kty, crv, x, d } ← never sent to server
// 2. Register with your public key (no private_key_jwk returned)
const identity = await client.register({
agent_name: "Claude",
agent_model: "claude-opus-4-6",
agent_provider: "Anthropic",
agent_purpose: "Research assistant",
public_key_jwk: keyPair.publicKeyJwk,
})
// identity.did — "did:key:z6Mk..."
// identity.credential — VC-JWT
// identity.key_origin — "client_provided"
// 3. Request a challenge
const challenge = await client.challenge(identity.did)
// 4. Sign the nonce with your private key
const signature = await AuthAgents.signChallenge(
keyPair.privateKeyJwk,
challenge.nonce
)
// 5. Authenticate and receive a fresh credential
const auth = await client.authenticate({
challenge_id: challenge.challenge_id,
did: identity.did,
signature,
})
if (auth.valid) {
console.log(auth.credential) // fresh VC-JWT
console.log(auth.session_token) // session ID
}Website Integration (Next.js)
// app/api/auth/agent-login/route.ts
import { AuthAgents } from "auth-agents"
const authAgents = new AuthAgents()
export async function POST(request: Request) {
const { credential } = await request.json()
const result = await authAgents.verify(credential)
if (!result.valid) {
return Response.json({ error: result.message }, { status: 401 })
}
// Create a session with the verified agent identity
const sessionId = crypto.randomUUID()
await db.insert("sessions", {
session_id: sessionId,
did: result.did,
agent_name: result.agent_name,
agent_model: result.agent_model,
key_origin: result.key_origin,
expires_at: result.expires_at,
})
return Response.json({ authenticated: true, session_id: sessionId })
}Website Integration (Express.js)
import express from "express"
import { AuthAgents } from "auth-agents"
const app = express()
const authAgents = new AuthAgents()
app.post("/auth/agent-login", express.json(), async (req, res) => {
const { credential } = req.body
const result = await authAgents.verify(credential)
if (!result.valid) {
return res.status(401).json({ error: result.message })
}
// Agent verified — create session
req.session.agent = {
did: result.did,
name: result.agent_name,
model: result.agent_model,
key_origin: result.key_origin,
}
res.json({ authenticated: true, agent_name: result.agent_name })
})API
new AuthAgents(config?)
config.baseUrl— API base URL (default:https://auth.usevigil.dev). The SDK enforces HTTPS for all API communication. HTTP is only allowed forlocalhostduring development.
AuthAgents.generateKeyPair() — Static
Generate a local Ed25519 key pair using the Web Crypto API. No network call.
Returns Ed25519KeyPair:
{
publicKeyJwk: { kty: string; crv: string; x: string }
privateKeyJwk: { kty: string; crv: string; x: string; d: string }
}AuthAgents.signChallenge(privateKeyJwk, nonce) — Static
Sign an authentication challenge nonce with an Ed25519 private key. No network call.
privateKeyJwk— JWK withdparameter (the private key fromgenerateKeyPair()or fromregister())nonce— the nonce string fromclient.challenge()
Returns a Promise<string> — base64url-encoded Ed25519 signature.
client.verify(credential) — Verify a VC-JWT credential
Returns VerifyResult on success:
{
valid: true
did: string
agent_name: string | null
agent_model: string | null
agent_provider: string | null
agent_purpose: string | null
key_fingerprint: string | null
key_origin: string | null // "server_generated" | "client_provided"
issued_at: string | null
expires_at: string | null
}Returns VerifyError (HTTP 401) without throwing:
{
valid: false
error: "credential_expired" | "invalid_issuer" | "signature_invalid" | "credential_revoked"
message: string
}client.register(input) — Register a new agent identity
input.public_key_jwk is optional. Omit it for server-generated keys (Flow A). Provide it for BYOK (Flow B).
You can also pass:
metadata— key/value metadata map (max key/value sizes enforced server-side)
Returns RegisterResult:
{
did: string
credential: string
key_fingerprint: string
key_origin?: "server_generated" | "client_provided"
private_key_jwk?: { kty, crv, x, d } // only present for server_generated
}Registration always returns a credential with the server default lifetime (24 hours).
If you need a different lifetime, pass credential_expires_in to client.challenge(...).
client.challenge(did, site_id?, credential_expires_in?) — Request an auth challenge
site_id is optional and scopes the challenge/session when your deployment uses site-level org provisioning.
credential_expires_in is optional and allows website developers to set the credential lifetime in seconds for the resulting authentication. Use 0 for non-expiring credentials. Min 300, max 2592000.
client.authenticate(input) — Submit signed challenge
input contains challenge_id, did, and signature (base64url-encoded).
The credential returned from client.authenticate(...) uses the lifetime chosen in the preceding client.challenge(...) call.
Documentation
Full API reference at usevigil.dev/docs
License
MIT - Agent Auth
