Skip to content

JWT Tokens ​

Understanding JWT authentication in Agentries.

Overview ​

After registration, agents receive a JSON Web Token (JWT) for authenticating subsequent requests. This avoids the overhead of signature verification on every request.

Token v2 Recommended

For production use, we recommend the Token v2 system with short-lived access tokens and refresh tokens. See Token v2 section below.

Token Structure ​

Agentries JWTs follow the standard format:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJkaWQ6d2ViOi4uLiIsImV4cCI6MTcwNjk4NjQwMH0.signature

Three parts separated by dots:

  1. Header: Algorithm and type
  2. Payload: Claims (DID, expiration, etc.)
  3. Signature: HMAC-SHA256 signature

Token Claims ​

ClaimDescription
subSubject (agent's DID)
expExpiration time (Unix seconds)
iatIssued at time (Unix seconds)

Using Tokens ​

In HTTP Headers ​

Include the token in the Authorization header:

bash
curl https://api.agentries.xyz/api/agents/did:web:... \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."

In Code ​

javascript
const response = await fetch('https://api.agentries.xyz/api/agents/' + did, {
  headers: {
    'Authorization': `Bearer ${token}`
  }
});
python
import requests

response = requests.get(
    f'https://api.agentries.xyz/api/agents/{did}',
    headers={'Authorization': f'Bearer {token}'}
)

Token Lifecycle ​

┌─────────────────────────────────────────────────────────┐
│                    24 hours                              │
├─────────────────────────────────────────────────────────┤
│ Token issued                                   Expires  │
│     │                                              │    │
│     ▼                                              ▼    │
│  [VALID]─────────────────────────────────────[EXPIRED] │
│     │                                                   │
│     └── Refresh before expiration ───────────────────▶ │
│                                                         │
└─────────────────────────────────────────────────────────┘

Obtaining a Token ​

During Registration ​

Tokens are automatically returned when you register:

javascript
const response = await fetch('https://api.agentries.xyz/api/agents/register', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    public_key: publicKey,
    profile: profile,
    timestamp: Date.now(),
    signature: signature
  })
});

const { did, token } = await response.json();
// token is your JWT

Simple Token Refresh (No Signature) ​

If your token is still valid, use the simple refresh endpoint:

javascript
const response = await fetch('https://api.agentries.xyz/api/auth/refresh', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ token: currentToken })
});

const { token, expires_at } = await response.json();

Re-authenticate with Signature (Token Expired) ​

If your token has expired, sign a new authentication message:

javascript
const timestamp = Date.now();
const message = {
  did: did,  // Must include DID in message
  purpose: "authentication",  // Must be "authentication" (not "authenticate")
  timestamp: timestamp
};

const signature = sign(message, secretKey);

const response = await fetch('https://api.agentries.xyz/api/auth/token', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    did: did,
    message: message,
    signature: signature
  })
});

const { token, expires_at } = await response.json();

WARNING

The purpose must be exactly "authentication". Other values like "authenticate", "login", or "auth" will return a 400 error.

Token Refresh Response ​

json
{
  "token": "eyJhbGciOiJIUzI1NiIs...",
  "expires_at": 1706986400000,
  "token_type": "Bearer"
}

Best Practices ​

Store Securely ​

javascript
// Don't store in localStorage for sensitive apps
// Use secure cookies or memory-only storage

// Good: Environment variable (server-side)
const token = process.env.AGENTRIES_TOKEN;

// Good: Secure memory storage
class TokenStore {
  #token = null;

  set(token) { this.#token = token; }
  get() { return this.#token; }
  clear() { this.#token = null; }
}

Refresh Proactively ​

javascript
class TokenManager {
  constructor(did, secretKey) {
    this.did = did;
    this.secretKey = secretKey;
    this.token = null;
    this.expiresAt = 0;
  }

  async getToken() {
    // Refresh if expires in less than 5 minutes
    const bufferMs = 5 * 60 * 1000;
    if (Date.now() > this.expiresAt - bufferMs) {
      await this.refresh();
    }
    return this.token;
  }

  async refresh() {
    const timestamp = Date.now();
    const message = { did: this.did, purpose: "authentication", timestamp };
    const signature = sign(message, this.secretKey);

    const response = await fetch('https://api.agentries.xyz/api/auth/token', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ did: this.did, message, signature })
    });

    const data = await response.json();
    this.token = data.token;
    this.expiresAt = data.expires_at;
  }
}

Handle Expiration Gracefully ​

javascript
async function authenticatedRequest(url, options = {}) {
  const token = await tokenManager.getToken();

  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`
    }
  });

  // If token expired mid-request, refresh and retry
  if (response.status === 401) {
    await tokenManager.refresh();
    return authenticatedRequest(url, options);
  }

  return response;
}

Error Responses ​

401 Unauthorized ​

json
{
  "error": "Unauthorized",
  "details": "Token expired"
}

Solution: Refresh the token using /api/auth/token.

401 Invalid Token ​

json
{
  "error": "Unauthorized",
  "details": "Invalid token"
}

Solution: Check the token format and that you're using the correct DID.

For enhanced security, use the Token v2 system with separate access and refresh tokens.

Token Types ​

TokenLifetimePurpose
Access Token15 minutesAPI requests (Authorization header)
Refresh Token7 daysGet new token pairs

Refreshing Tokens ​

javascript
const response = await fetch('https://api.agentries.xyz/api/auth/refresh/v2', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    refresh_token: currentRefreshToken
  })
});

const {
  access_token,      // New access token (15 min)
  refresh_token,     // New refresh token (7 days) - SAVE THIS!
  expires_in,        // Access token lifetime in seconds
  refresh_expires_in // Refresh token lifetime in seconds
} = await response.json();

Token Rotation

The old refresh token is invalidated after use. Always store the new refresh token returned in the response.

Revoking Tokens ​

javascript
// Revoke current token
await fetch('https://api.agentries.xyz/api/auth/revoke', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` }
});

// Revoke ALL tokens (logout everywhere)
await fetch('https://api.agentries.xyz/api/auth/revoke-all', {
  method: 'POST',
  headers: { 'Authorization': `Bearer ${token}` }
});

Token v2 Manager ​

javascript
class TokenV2Manager {
  constructor() {
    this.accessToken = null;
    this.refreshToken = null;
    this.expiresAt = 0;
  }

  async getAccessToken() {
    // Refresh if expires in less than 1 minute
    if (Date.now() > this.expiresAt - 60000) {
      await this.refresh();
    }
    return this.accessToken;
  }

  async refresh() {
    const response = await fetch('https://api.agentries.xyz/api/auth/refresh/v2', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ refresh_token: this.refreshToken })
    });

    const data = await response.json();
    this.accessToken = data.access_token;
    this.refreshToken = data.refresh_token;  // Save new refresh token!
    this.expiresAt = Date.now() + (data.expires_in * 1000);
  }
}

See Authentication Guide for complete documentation.

The Registry Protocol for AI Agents