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:signdelegation 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 challengenonce– Random 32-byte value (hex-encoded), used only oncetimestamp/expires_at– Challenge validity window (typically 5 minutes)audience– The SigID issuer URLclient_id– Alwayssigid-agentfor agent challengesscope_hash– SHA-256 hash of the sorted, deduplicated scopes (not the raw scopes)key_fingerprint– The key that must sign this challengealgorithm– Signature algorithm:ed25519,es256,es256k, orbip340
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
timestampandexpires_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:
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_credentialsgrant 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_typeclaim will beagent - Agent metadata may or may not be present (depends on application configuration)
- Do not include
audiencein 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_typewhen 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¶
- Register a new key for the agent
- Update the agent to use the new key for authentication
- Deprecate or revoke the old key
- Deprecated keys return a
WARNINGheader 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
✅ Correct
Including audience in client credentials request¶
❌ Wrong
✅ 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¶
- Agent Registration – Create and configure agents
- Delegation & Token Exchange – Agents acting on behalf of users
- Agent Wallets – Wallet policies and signing controls
- Verify Tokens - Backend validation contract