A robust, enterprise-grade Node.js library for Traylinx Sentinel Agent-to-Agent (A2A) authentication. This client provides secure token management, automatic retry logic, comprehensive error handling, and seamless integration with Express.js applications.
- ๐ Dual Token Authentication: Handles both
access_tokenandagent_secret_tokenwith automatic refresh - ๐ก๏ธ Enterprise Security: Input validation, secure credential handling, and comprehensive error management
- โก High Performance: Connection pooling, automatic retries with exponential backoff, and efficient token caching
- ๐ Async/Await Ready: Modern JavaScript with Promise-based API and full async/await support
- ๐ฏ Express.js Integration: Simple middleware for protecting endpoints with A2A authentication
- ๐ก JSON-RPC Support: Full support for A2A RPC method calls with automatic credential detection
- ๐ง Zero Configuration: Works with environment variables out of the box
- ๐ Production Ready: Configurable logging, monitoring, and comprehensive error handling
npm install @traylinx/auth-clientyarn add @traylinx/auth-client- Node.js 14.0 or higher
axios>= 0.21.0uuid>= 8.0.0joi>= 17.0.0
const { makeA2ARequest } = require('@traylinx/auth-client');
// Set environment variables: TRAYLINX_CLIENT_ID, TRAYLINX_CLIENT_SECRET,
// TRAYLINX_API_BASE_URL, TRAYLINX_AGENT_USER_ID
// Make authenticated request to another agent
const response = await makeA2ARequest('GET', 'https://other-agent.com/api/data');
console.log(response); // JSON response from the agentSet these environment variables for your agent:
export TRAYLINX_CLIENT_ID="your-client-id"
export TRAYLINX_CLIENT_SECRET="your-client-secret"
export TRAYLINX_API_BASE_URL="https://auth.traylinx.com"
export TRAYLINX_AGENT_USER_ID="12345678-1234-1234-1234-123456789abc"const { TraylinxAuthClient } = require('@traylinx/auth-client');
const client = new TraylinxAuthClient(
'your-client-id',
'your-client-secret',
'https://auth.traylinx.com',
'12345678-1234-1234-1234-123456789abc',
{
timeout: 30000, // Request timeout in milliseconds
maxRetries: 3, // Maximum retry attempts
retryDelay: 1000, // Base delay between retries
cacheTokens: true, // Enable token caching
logLevel: 'INFO' // Logging level
}
);const { makeA2ARequest } = require('@traylinx/auth-client');
// GET request
const data = await makeA2ARequest('GET', 'https://other-agent.com/api/users');
// POST request with JSON data
const result = await makeA2ARequest('POST', 'https://other-agent.com/api/process', {
json: { items: ['item1', 'item2'] },
timeout: 60000
});
// PUT request with custom headers
const response = await makeA2ARequest('PUT', 'https://other-agent.com/api/update/123', {
json: { status: 'completed' },
headers: { 'X-Custom-Header': 'value' }
});const { getAgentRequestHeaders } = require('@traylinx/auth-client');
const axios = require('axios');
// Get headers for calling other agents
const headers = await getAgentRequestHeaders();
// Make authenticated request
const response = await axios.get('https://other-agent.com/api/data', { headers });
// Headers include:
// {
// "X-Agent-Secret-Token": "your-agent-secret-token",
// "X-Agent-User-Id": "your-agent-user-id"
// }const express = require('express');
const { requireA2AAuth } = require('@traylinx/auth-client');
const app = express();
app.get('/protected', requireA2AAuth, (req, res) => {
res.json({ message: 'This endpoint requires A2A authentication' });
});
app.post('/process', requireA2AAuth, (req, res) => {
// This endpoint is automatically protected
res.json({ processed: req.body });
});const express = require('express');
const { validateA2ARequest } = require('@traylinx/auth-client');
const app = express();
app.post('/manual-validation', async (req, res) => {
try {
const isValid = await validateA2ARequest(req.headers);
if (!isValid) {
return res.status(401).json({ error: 'Invalid A2A token' });
}
res.json({ message: 'Token is valid' });
} catch (error) {
console.error('Validation error:', error);
res.status(500).json({ error: 'Authentication service unavailable' });
}
});const { requireDualAuth, detectAuthMode } = require('@traylinx/auth-client');
app.get('/flexible-auth', requireDualAuth, (req, res) => {
// Supports both Bearer tokens and custom headers
const authMode = detectAuthMode(req.headers);
res.json({ authMode, message: 'Authenticated successfully' });
});const { TraylinxAuthClient } = require('@traylinx/auth-client');
// Create client with custom configuration
const client = new TraylinxAuthClient(
'client-id',
'client-secret',
'https://auth.traylinx.com',
'agent-user-id',
{
timeout: 60000,
maxRetries: 5,
retryDelay: 2000
}
);
// Get individual tokens
const accessToken = await client.getAccessToken();
const agentSecretToken = await client.getAgentSecretToken();
// Get different header types
const authHeaders = await client.getRequestHeaders(); // For auth service calls
const agentHeaders = await client.getAgentRequestHeaders(); // For agent calls
const a2aHeaders = await client.getA2AHeaders(); // A2A-compatible format
// Validate incoming tokens
const isValid = await client.validateToken('incoming-token', 'sender-agent-id');const { TraylinxAuthClient } = require('@traylinx/auth-client');
const client = new TraylinxAuthClient();
// Built-in RPC methods
const result = await client.rpcIntrospectToken('token-to-validate', 'agent-id');
const capabilities = await client.rpcGetCapabilities();
const health = await client.rpcHealthCheck();
// Custom RPC calls
const response = await client.rpcCall(
'custom_method',
{ param1: 'value1' },
'https://custom-agent.com/a2a/rpc'
);
// RPC call with explicit credential control
const response = await client.rpcCall(
'auth_service_method',
{},
null, // Use default auth service URL
false // Uses only access_token
);const {
TraylinxAuthClient,
AuthenticationError,
NetworkError,
ValidationError,
TokenExpiredError
} = require('@traylinx/auth-client');
try {
const client = new TraylinxAuthClient(
'invalid-id',
'invalid-secret'
);
const response = await client.rpcHealthCheck();
} catch (error) {
if (error instanceof ValidationError) {
console.error(`Configuration error: ${error.message}`);
console.error(`Error code: ${error.code}`);
} else if (error instanceof AuthenticationError) {
console.error(`Authentication failed: ${error.message}`);
console.error(`Status code: ${error.statusCode}`);
} else if (error instanceof NetworkError) {
console.error(`Network error: ${error.message}`);
if (error.code === 'TIMEOUT') {
console.error('Request timed out - check network connectivity');
} else if (error.code === 'RATE_LIMIT') {
console.error('Rate limited - retry after delay');
}
} else if (error instanceof TokenExpiredError) {
console.error(`Token expired: ${error.message}`);
// Token will be automatically refreshed on next request
}
}const { TraylinxAuthClient } = require('@traylinx/auth-client');
// Thread-safe client usage
const client = new TraylinxAuthClient();
async function workerFunction(workerId) {
try {
// Each async function can safely use the same client
const headers = await client.getAgentRequestHeaders();
const response = await makeA2ARequest('GET', `https://api.com/data/${workerId}`);
console.log(`Worker ${workerId}:`, response);
} catch (error) {
console.error(`Worker ${workerId} error:`, error);
}
}
// Create multiple concurrent requests
const promises = [];
for (let i = 0; i < 10; i++) {
promises.push(workerFunction(i));
}
// Wait for all requests to complete
await Promise.all(promises);Make an authenticated A2A request to another agent.
Parameters:
method(string): HTTP method (GET, POST, PUT, DELETE, etc.)url(string): Target agent's URLoptions(Object, optional): Additional options for axios
Returns:
Promise<Object>: JSON response from the target agent
Throws:
NetworkError: For network-related issuesAuthenticationError: For authentication failuresValidationError: For invalid parameters
Example:
const response = await makeA2ARequest('POST', 'https://agent.com/api', {
json: { key: 'value' }
});Returns headers for calling the auth service (includes access_token).
Returns:
{
"Authorization": "Bearer <access_token>",
"X-Agent-Secret-Token": "<agent_secret_token>",
"X-Agent-User-Id": "<agent_user_id>"
}Returns headers for calling other agents (ONLY agent_secret_token).
Returns:
{
"X-Agent-Secret-Token": "<agent_secret_token>",
"X-Agent-User-Id": "<agent_user_id>"
}Returns A2A-compatible headers using Bearer token format.
Returns:
{
"Authorization": "Bearer <agent_secret_token>",
"X-Agent-User-Id": "<agent_user_id>"
}Express.js middleware that protects endpoints with A2A authentication.
Parameters:
req(Object): Express request objectres(Object): Express response objectnext(Function): Express next function
Example:
app.get('/protected', requireA2AAuth, (req, res) => {
res.json({ message: 'Protected' });
});Enhanced middleware supporting both Bearer tokens and custom headers.
Validates incoming A2A request headers (custom format).
Parameters:
headers(Object): Request headers
Returns:
Promise<boolean>: True if valid, false otherwise
Validates incoming requests supporting both Bearer tokens and custom headers.
Detect authentication mode from request headers.
Returns:
string: 'bearer', 'custom', or 'none'
new TraylinxAuthClient(
clientId, // OAuth client ID
clientSecret, // OAuth client secret
apiBaseUrl, // Traylinx API base URL
agentUserId, // Agent user UUID
options = {} // Additional configuration
)Parameters:
- All parameters default to corresponding environment variables
options.timeout(number): Request timeout in milliseconds (default: 30000)options.maxRetries(number): Maximum retry attempts (default: 3)options.retryDelay(number): Base retry delay in milliseconds (default: 1000)options.cacheTokens(boolean): Enable token caching (default: true)options.logLevel(string): Logging level - DEBUG, INFO, WARN, ERROR (default: "INFO")
Throws:
ValidationError: If configuration parameters are invalid
Get current access token for calling auth service.
Returns:
Promise<string>: Valid access token
Throws:
TokenExpiredError: If token is unavailableAuthenticationError: If token fetch fails
Get current agent secret token for agent-to-agent communication.
Returns:
Promise<string>: Valid agent secret token
Throws:
TokenExpiredError: If token is unavailableAuthenticationError: If token fetch fails
Get headers for calling the auth service (includes access_token).
Get headers for calling other agents (ONLY agent_secret_token).
Get A2A-compatible authentication headers using Bearer token format.
Validate an agent secret token against the auth service.
Parameters:
agentSecretToken(string): Token to validateagentUserId(string): Agent user ID associated with token
Returns:
Promise<boolean>: True if token is valid and active
Throws:
AuthenticationError: If validation request failsNetworkError: For network-related issues
Validate A2A request supporting both Bearer tokens and custom headers.
Detect authentication mode from request headers.
Make a JSON-RPC call with automatic credential detection.
Parameters:
method(string): RPC method nameparams(Object): RPC method parametersrpcUrl(string, optional): Custom RPC endpoint URLincludeAgentCredentials(boolean, optional): Whether to include agent credentials
Returns:
Promise<Object>: JSON-RPC response
Throws:
ValidationError: For invalid RPC requests or parametersAuthenticationError: For authentication failuresNetworkError: For network issuesTraylinxAuthError: For RPC-specific errors
Introspect a token via JSON-RPC.
Get agent capabilities via JSON-RPC.
Perform health check via JSON-RPC.
Base error class for all TraylinxAuthClient errors.
Properties:
code(string): Specific error codestatusCode(number): HTTP status code (if applicable)
Thrown for input validation failures.
Thrown for authentication-related failures.
Thrown when tokens are expired or unavailable.
Thrown for network-related issues (timeouts, connection errors, etc.).
Traylinx uses a dual-token authentication system for enhanced security:
access_token: Used for calling Traylinx auth service endpointsagent_secret_token: Used for agent-to-agent communication
sequenceDiagram
participant Client as TraylinxAuthClient
participant Auth as Traylinx Sentinel API
participant Agent as Target Agent
Note over Client,Auth: Initial Authentication
Client->>Auth: POST /oauth/token (client_credentials)
Auth-->>Client: {access_token, agent_secret_token, expires_in}
Note over Client,Auth: Calling Auth Service
Client->>Auth: GET /a2a/rpc (Authorization: Bearer access_token)
Auth-->>Client: Response
Note over Client,Agent: Calling Other Agents
Client->>Agent: POST /endpoint (X-Agent-Secret-Token: agent_secret_token)
Agent->>Auth: Validate token
Auth-->>Agent: Token valid
Agent-->>Client: Response
| Scenario | access_token | agent_secret_token | Headers |
|---|---|---|---|
| Auth service calls | โ Required | โ Not used | Authorization: Bearer <access_token> |
| Agent-to-agent calls | โ Not used | โ Required | X-Agent-Secret-Token: <agent_secret_token> |
| A2A compatible calls | โ Not used | โ Required | Authorization: Bearer <agent_secret_token> |
| Token validation | โ Required | โ Not used | Authorization: Bearer <access_token> |
- Token Acquisition: Automatically fetches tokens using OAuth2
client_credentialsgrant - Token Caching: Tokens cached in memory with automatic expiration handling
- Automatic Refresh: Expired tokens refreshed automatically before requests
- Error Recovery: Handles token expiration and authentication failures gracefully
Example token response:
{
"access_token": "eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...",
"agent_secret_token": "TqlJuJi5aJa7lz8rg9zWjbRDChND8m9PMr4bsn...",
"token_type": "Bearer",
"expires_in": 7200,
"scope": "a2a"
}The client provides robust error handling with specific error types:
const {
TraylinxAuthError, // Base error
ValidationError, // Configuration/input validation
AuthenticationError, // Authentication failures
TokenExpiredError, // Token expiration
NetworkError // Network/connectivity issues
} = require('@traylinx/auth-client');- Exponential Backoff: Automatic retries with increasing delays
- Configurable Retries: Set
maxRetriesandretryDelayoptions - Smart Retry Logic: Only retries on transient failures (429, 5xx errors)
- Connection Management: Efficient connection reuse for better performance
| Error Type | HTTP Status | Retry | Description |
|---|---|---|---|
ValidationError |
400 | โ | Invalid configuration or parameters |
AuthenticationError |
401 | โ | Invalid credentials or expired tokens |
NetworkError (Rate Limit) |
429 | โ | Rate limiting - automatic retry with backoff |
NetworkError (Timeout) |
408 | โ | Request timeout - configurable retry |
NetworkError (Server Error) |
5xx | โ | Server errors - automatic retry |
NetworkError (Connection) |
0 | โ | Connection failures - automatic retry |
const { TraylinxAuthClient, NetworkError } = require('@traylinx/auth-client');
const client = new TraylinxAuthClient();
try {
const response = await client.rpcHealthCheck();
} catch (error) {
if (error instanceof NetworkError) {
if (error.code === 'RATE_LIMIT') {
console.warn(`Rate limited: ${error.message}`);
// Implement backoff strategy
} else if (error.code === 'TIMEOUT') {
console.error(`Request timeout: ${error.message}`);
// Check network connectivity
} else {
console.error(`Network error: ${error.message}`);
}
}
}โ DO:
- Store credentials in environment variables or secure vaults
- Use different credentials for different environments (dev/staging/prod)
- Regularly rotate client credentials
- Monitor authentication failures and unusual patterns
- Use HTTPS for all communications
โ DON'T:
- Hard-code credentials in source code
- Log sensitive data (tokens, secrets, passwords)
- Share credentials between different applications
- Use production credentials in development/testing
// โ
Good: Use environment variables
const client = new TraylinxAuthClient(); // Reads from env vars
// โ
Good: Use secure credential management
const client = new TraylinxAuthClient(
process.env.TRAYLINX_CLIENT_ID,
await getSecret('traylinx_client_secret'),
process.env.TRAYLINX_API_BASE_URL,
process.env.TRAYLINX_AGENT_USER_ID
);
// โ Bad: Hard-coded credentials
const client = new TraylinxAuthClient(
'hardcoded-id', // Never do this!
'hardcoded-secret' // Never do this!
);- TLS/HTTPS: All communications use HTTPS with certificate validation
- Request Signing: Tokens provide request authenticity
- Timeout Protection: Configurable timeouts prevent hanging requests
- Rate Limiting: Built-in protection against rate limiting
// โ
Safe logging - no sensitive data
console.log('Authentication successful for agent', agentUserId);
console.error('Authentication failed with status', response.status);
// โ Unsafe logging - exposes sensitive data
console.log('Token:', accessToken); // Never log tokens!
console.debug('Secret:', clientSecret); // Never log secrets!Problem: AuthenticationError: Invalid credentials
Solutions:
- Verify environment variables are set correctly
- Check clientId and clientSecret are valid
- Ensure API base URL is correct
- Verify agentUserId is a valid UUID
# Check environment variables
echo $TRAYLINX_CLIENT_ID
echo $TRAYLINX_CLIENT_SECRET
echo $TRAYLINX_API_BASE_URL
echo $TRAYLINX_AGENT_USER_IDProblem: NetworkError: Connection failed
Solutions:
- Check network connectivity to API endpoint
- Verify firewall/proxy settings
- Increase timeout value
- Check DNS resolution
// Test connectivity
const axios = require('axios');
const response = await axios.get('https://your-api-base-url/health');
console.log(response.status);Problem: ValidationError: Configuration validation failed
Solutions:
- Ensure agentUserId is a valid UUID format
- Verify API base URL is a valid HTTPS URL
- Check all required parameters are provided
// Validate UUID format
const { validate: isUUID } = require('uuid');
console.log(isUUID('your-agent-user-id')); // Should be trueProblem: TokenExpiredError: Token is not available
Solutions:
- Check if initial authentication succeeded
- Verify network connectivity during token refresh
- Check if credentials are still valid
// Enable debug logging
const client = new TraylinxAuthClient(
clientId,
clientSecret,
apiBaseUrl,
agentUserId,
{ logLevel: 'DEBUG' }
);
// This will show detailed request/response information// Optimize for high-throughput scenarios
const client = new TraylinxAuthClient(
clientId,
clientSecret,
apiBaseUrl,
agentUserId,
{
timeout: 60000, // Longer timeout for slow networks
maxRetries: 5, // More retries for reliability
retryDelay: 500, // Faster initial retry
cacheTokens: true // Enable token caching
}
);A: The access_token is used for calling Traylinx auth service endpoints, while agent_secret_token is used for agent-to-agent communication. They serve different purposes in the dual-token authentication system.
A: Token expiration is handled automatically. The client will refresh tokens before they expire and retry failed requests with fresh tokens.
A: Yes! The client is designed for reuse and can safely handle multiple concurrent requests. Token management is handled internally.
A: Use the maxRetries and retryDelay options:
const client = new TraylinxAuthClient(
clientId, clientSecret, apiBaseUrl, agentUserId,
{
maxRetries: 5, // Retry up to 5 times
retryDelay: 2000 // Start with 2-second delay
}
);A: The client will retry requests with exponential backoff. If all retries fail, it will throw a NetworkError with details about the failure.
A: Use the requireA2AAuth middleware or validateA2ARequest() function:
app.post('/endpoint', requireA2AAuth, (req, res) => {
res.json({ status: 'authenticated' });
});# Clone the repository
git clone https://github.com/traylinx/traylinx-auth-client-js.git
cd traylinx-auth-client-js
# Install dependencies
npm install
# Run tests
npm test
# Run tests with coverage
npm run test:coverage
# Run linting
npm run lint
# Run type checking (if TypeScript definitions exist)
npm run type-check# Run all tests
npm test
# Run tests with coverage report
npm run test:coverage
# Run specific test file
npm test -- --testPathPattern=client.test.js
# Run tests in watch mode
npm test -- --watch
# Run tests with verbose output
npm test -- --verbose# Lint code with ESLint
npm run lint
# Fix linting issues automatically
npm run lint:fix
# Security audit
npm audit
# Check for outdated dependencies
npm outdated# Check package contents
npm pack --dry-run
# Test publish to npm (dry run)
npm publish --dry-run
# Publish to npm
npm publish --access publictraylinx_auth_client_js/
โโโ src/
โ โโโ index.js # Public API exports and high-level functions
โ โโโ client.js # Core TraylinxAuthClient class
โ โโโ validation.js # Input validation with Joi
โ โโโ errors.js # Custom error hierarchy
โโโ tests/
โ โโโ client.test.js # Client class tests
โ โโโ validation.test.js # Validation tests
โ โโโ errors.test.js # Error handling tests
โ โโโ index.test.js # High-level function tests
โโโ package.json # Project configuration and dependencies
โโโ README.md # This file
โโโ CHANGELOG.md # Version history
โโโ CONTRIBUTING.md # Contribution guidelines
โโโ SECURITY.md # Security policy
โโโ LICENSE # MIT license
- Create Feature Branch:
git checkout -b feature/new-feature - Write Tests First: Add tests in appropriate test file
- Implement Feature: Add implementation with proper JSDoc comments
- Update Documentation: Update README and JSDoc comments
- Run Quality Checks: Ensure tests pass and code quality is maintained
- Submit Pull Request: Include description of changes and test results
- Unit Tests: Test individual functions and methods
- Integration Tests: Test end-to-end authentication flows
- Error Tests: Test all error conditions and edge cases
- Mock External Services: Use mocks for HTTP requests to auth service
- Async Safety Tests: Test concurrent access patterns
We welcome contributions! Please see CONTRIBUTING.md for detailed guidelines.
- Fork the repository on GitHub
- Clone your fork locally
- Create a feature branch from
main - Make your changes with tests
- Run the test suite to ensure everything works
- Submit a pull request with a clear description
- ๐ Bug Fixes: Fix issues and improve reliability
- โจ New Features: Add new functionality
- ๐ Documentation: Improve docs and examples
- ๐ง Performance: Optimize performance and efficiency
- ๐งช Tests: Add or improve test coverage
- ๐จ Code Quality: Refactoring and code improvements
- Follow JavaScript Standard Style guidelines
- Add JSDoc comments for all public functions
- Write comprehensive tests for new features
- Maintain test coverage above 90%
- Use meaningful variable and function names
This project is licensed under the MIT License - see the LICENSE file for details.
- โ Commercial use allowed
- โ Modification allowed
- โ Distribution allowed
- โ Private use allowed
- โ No warranty provided
- โ No liability accepted
- ๐ Documentation: Traylinx Developer Docs
- ๐ Bug Reports: GitHub Issues
- ๐ฌ Discussions: GitHub Discussions
- ๐ง Email Support: dev@traylinx.com
When reporting issues, please include:
- Node.js version and operating system
- Library version (
npm list @traylinx/auth-client) - Minimal code example that reproduces the issue
- Full error stack trace if applicable
- Expected vs actual behavior
We welcome feature requests! Please:
- Check existing issues to avoid duplicates
- Describe the use case and problem you're solving
- Provide examples of how the feature would be used
- Consider contributing the implementation
For security-related issues, please follow our Security Policy:
- ๐ Private Disclosure: Email security@traylinx.com
- โฑ๏ธ Response Time: We aim to respond within 24 hours
- ๐ก๏ธ Responsible Disclosure: We follow coordinated disclosure practices
Made with โค๏ธ by the Traylinx Team