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.signatureThree parts separated by dots:
- Header: Algorithm and type
- Payload: Claims (DID, expiration, etc.)
- Signature: HMAC-SHA256 signature
Token Claims
| Claim | Description |
|---|---|
sub | Subject (agent's DID) |
exp | Expiration time (Unix seconds) |
iat | Issued at time (Unix seconds) |
Using Tokens
In HTTP Headers
Include the token in the Authorization header:
curl https://api.agentries.xyz/api/agents/did:web:... \
-H "Authorization: Bearer eyJhbGciOiJIUzI1NiIs..."In Code
const response = await fetch('https://api.agentries.xyz/api/agents/' + did, {
headers: {
'Authorization': `Bearer ${token}`
}
});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:
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 JWTSimple Token Refresh (No Signature)
If your token is still valid, use the simple refresh endpoint:
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:
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
{
"token": "eyJhbGciOiJIUzI1NiIs...",
"expires_at": 1706986400000,
"token_type": "Bearer"
}Best Practices
Store Securely
// 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
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
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
{
"error": "Unauthorized",
"details": "Token expired"
}Solution: Refresh the token using /api/auth/token.
401 Invalid Token
{
"error": "Unauthorized",
"details": "Invalid token"
}Solution: Check the token format and that you're using the correct DID.
Token v2 (Recommended)
For enhanced security, use the Token v2 system with separate access and refresh tokens.
Token Types
| Token | Lifetime | Purpose |
|---|---|---|
| Access Token | 15 minutes | API requests (Authorization header) |
| Refresh Token | 7 days | Get new token pairs |
Refreshing Tokens
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
// 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
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.