Skip to content

Agent Authentication

This page explains how an agent authenticates as itself to SigID and receives an agent-scoped access token. For delegation (agents acting on behalf of users), see Delegation & Token Exchange.

What this page is for

  • Authenticating an autonomous agent using challenge-response
  • Setting up internal services with client credentials
  • Understanding the token shape and validation requirements for agent tokens
  • Troubleshooting common authentication failures

This page assumes you have already registered an agent. See Agent Self-Serve Quickstart for cold bootstrap, or Agent Registration for tenant-scoped create with an existing bearer.

Agent identity

An agent identity consists of:

  • Agent ID – UUID identifying the agent
  • Keys – One or more registered signing keys (Ed25519, ES256, ES256K, BIP340)
  • Capabilities – Declared agent capabilities (e.g., web, mcp)
  • Key fingerprint – Hex-encoded SHA-256 hash of the public key

See Agent Registration to create an agent identity.

Before you start

You will need:

  • Agent record – A registered agent in your tenant
  • Active key – A registered, active key for challenge-response, or a confidential OAuth application for client credentials
  • Key fingerprint – Hex-encoded SHA-256 fingerprint of the public key
  • Allowed scopes – Scopes must be in the agent allowlist: openid, profile, email, offline_access, wallet:sign, vault:read
  • Wallet session bounds – A wallet:sign delegation needs a non-zero spend cap or at least one allowed recipient to receive on-chain session authority. Omitted/zero caps plus an empty recipient list remain off-chain-only during the v1 compatibility window and are rejected outright in v2.
  • Issuer URL – Your SigID issuer URL (e.g., https://auth.example.com)

Choose an authentication method

Method Use case Key requirements Token type
Challenge-response Autonomous agents that hold their own signing keys Registered public key, key fingerprint Agent-scoped access token
Client credentials Internal services, legacy automation Confidential OAuth application with client_credentials grant enabled Agent-scoped access token
Token exchange Agent acting on behalf of a user User token + agent token Delegated token (see Delegation & Token Exchange)

Challenge-response authentication

Challenge-response is the recommended method for autonomous agents. The agent proves possession of a registered signing key by signing a challenge from SigID.

1. Request a challenge

Request

curl -sS "$SIGID_ISSUER_URL/api/v1/agents/auth/challenge" \
  -X POST \
  -H "content-type: application/json" \
  -d '{
    "key_fingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
    "scope": "openid wallet:sign"
  }'

Response

{
  "challenge_id": "01234567-89ab-cdef-0123-456789abcdef",
  "nonce": "9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08",
  "timestamp": "2025-01-14T10:30:00Z",
  "expires_at": "2025-01-14T10:35:00Z",
  "audience": "https://auth.example.com",
  "client_id": "sigid-agent",
  "scope_hash": "a1b2c3d4...",
  "key_fingerprint": "0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
  "algorithm": "ed25519"
}

Field notes

  • challenge_id – Unique identifier for this challenge
  • nonce – Random 32-byte value (hex-encoded), used only once
  • timestamp / expires_at – Challenge validity window (typically 5 minutes)
  • audience – The SigID issuer URL
  • client_id – Always sigid-agent for agent challenges
  • scope_hash – SHA-256 hash of the sorted, deduplicated scopes (not the raw scopes)
  • key_fingerprint – The key that must sign this challenge
  • algorithm – Signature algorithm: ed25519, es256, es256k, or bip340

2. Build the canonical payload

SigID uses the SIGID-CHALLENGE-V1 format for signing. Canonicalize the challenge response as follows:

SIGID-CHALLENGE-V1
nonce:{nonce}
timestamp:{unix_seconds}
expires_at:{unix_seconds}
audience:{audience}
client_id:{client_id}
scope_hash:{scope_hash}
key_fingerprint:{key_fingerprint}
algorithm:{algorithm}

Example

SIGID-CHALLENGE-V1
nonce:9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08
timestamp:1736847800
expires_at:1736848100
audience:https://auth.example.com
client_id:sigid-agent
scope_hash:a1b2c3d4e5f6...
key_fingerprint:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
algorithm:ed25519

Important

  • Use unix seconds (not ISO8601) for timestamp and expires_at
  • Escape backslashes and control characters in string fields
  • Sign the exact byte sequence (UTF-8)

3. Sign the payload

Sign the canonicalized payload using your registered private key. The signature must be base64-encoded.

Example (Ed25519)

import base64
from nacl.signing import SigningKey

canonical = b"SIGID-CHALLENGE-V1\nnonce:..."
signing_key = SigningKey(bytes.fromhex(private_key_hex))
signature = signing_key.sign(canonical)
signature_b64 = base64.b64encode(signature.signature).decode()

4. Verify and get token

Request

curl -sS "$SIGID_ISSUER_URL/api/v1/agents/auth/verify" \
  -X POST \
  -H "content-type: application/json" \
  -d '{
    "challenge_id": "01234567-89ab-cdef-0123-456789abcdef",
    "signature": "base64-encoded-signature",
    "algorithm": "ed25519"
  }'

Success Response

{
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6I...",
  "refresh_token": "def50200...}",
  "token_type": "Bearer",
  "expires_in": 300,
  "scope": "openid wallet:sign"
}

Error Responses

Error Cause
challenge_expired Challenge expired (timestamp exceeds expires_at)
challenge_used Nonce already consumed (one-time use)
key_revoked Key fingerprint is revoked
key_not_found Key fingerprint not registered
invalid_signature Signature verification failed
invalid_scope Requested scope not in allowlist

5. Use the token

Include the access token in the Authorization header:

curl -H "Authorization: Bearer $ACCESS_TOKEN" \
  https://api.example.com/protected

Client credentials for internal agents

For internal services or legacy automation, use the standard OAuth client_credentials grant.

Requirements

  • The OAuth application must be confidential (has a client_secret)
  • The application must allow client_credentials grant type
  • The audience comes from the application configuration, not the request

Request

curl -sS "$SIGID_ISSUER_URL/oauth/token" \
  -u "$CLIENT_ID:$CLIENT_SECRET" \
  -H "content-type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=client_credentials"

Response

{
  "access_token": "eyJhbGciOiJFUzI1NiIsImtpZCI6I...",
  "token_type": "Bearer",
  "expires_in": 300,
  "scope": "openid"
}

Important

  • Client credentials tokens do not include a refresh token
  • The subject_type claim will be agent
  • Agent metadata may or may not be present (depends on application configuration)
  • Do not include audience in the request – it comes from the application config

Token shape and validation

Agent token claims

{
  "iss": "https://auth.example.com",
  "sub": "12345678-1234-1234-1234-123456789abc",
  "aud": "https://api.example.com",
  "exp": 1736848400,
  "iat": 1736848100,
  "jti": "01234567-89ab-cdef-0123-456789abcdef",
  "scope": "openid wallet:sign",
  "client_id": "sigid-agent",
  "subject_type": "agent",
  "agent": {
    "name": "My Agent",
    "capabilities": ["web", "mcp"]
  },
  "tenant_id": "tenant-uuid"
}

Validation requirements

Resource servers must validate:

Claim Validation
iss Must match your SigID issuer URL
aud Must match your resource server's audience
exp Must be in the future
sub Use with tenant_id as the principal key
subject_type Must be agent for agent-only APIs
scope Enforce required scopes for your API
tenant_id Prevent cross-tenant authorization
agent Present for challenge-response tokens, optional for client credentials

Critical

  • Always verify the signature, never decode JWT without verification
  • Never treat an agent token as a human user session
  • Always check subject_type when making authorization decisions
  • Never use email addresses as agent identifiers

See Verify Tokens for the complete validation contract.

Key lifecycle and failure cases

Key states

State Can authenticate? Notes
active ✅ Yes Normal state
deprecated ✅ Yes (with warning) Used during key rotation
revoked ❌ No Authentication fails with key_revoked

Key rotation

  1. Register a new key for the agent
  2. Update the agent to use the new key for authentication
  3. Deprecate or revoke the old key
  4. Deprecated keys return a WARNING header with sunset information

Failure cases

Scenario Error Recovery
Wrong key fingerprint key_not_found Use registered fingerprint
Revoked key key_revoked Register new key
Reused challenge challenge_used Request new challenge
Expired challenge challenge_expired Request new challenge
Invalid scope invalid_scope Use allowed scopes only

Common mistakes

Using scopes outside the allowlist

Wrong

"scope": "agent:read tools:execute"

Correct

"scope": "openid wallet:sign vault:read"

Including audience in client credentials request

Wrong

--data-urlencode "audience=https://api.example.com"

Correct

Audience comes from the OAuth application configuration.

Treating agent tokens as human sessions

Agent tokens have subject_type: "agent", not subject_type: "human". Always validate the subject_type claim before making authorization decisions.

Skipping signature verification

Never decode and trust a JWT without verifying its signature. Always use a proper JWT library or the Verify Tokens contract.

Reusing challenges

Challenges are single-use. Attempting to verify the same challenge twice will fail with challenge_used. Always request a fresh challenge for each authentication.

Next steps