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