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 Registration if you need to create an agent first.
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 - 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/auth/agent/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/auth/agent/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