Authentication Overview
ApiCharge uses a token-based authentication system where access tokens are obtained through the Purchase Flow and then used to authenticate API requests. The authentication system enforces both route access and rate limiting based on the purchased ApiCharge Subscription.
Authentication Flow Steps
- Client completes purchase flow and receives an unsigned access token
- Client signs the access token with their private key
- Client includes the signed token in API requests
- ApiCharge validates the token and enforces rate limits
- Request is forwarded to backend if authentication succeeds
Access Token Structure
After completing a purchase, clients receive an access token with this structure:
{
"signableEntity": {
"signingPubkey": "GBXDGHXCQVZUDDD4NNGBOGCXYQFKNBPIMJWW7GMUCQ2OJCPO7B3GIXWA",
"signature": null,
"expirationUnixTimeStamp": 1716384000,
"signableEntity": {
"signingPubkey": "GATCBZKXPNCVBKY5KLXSWLNUAXMUPARGW5JBSP75XYU6P3AK4JUMNXQ5",
"signature": "J8KpLzrDCF7Y4kqt9DLZLsCNapQ7SiwsCXf6EPfCxmD2kZEhf9m2LjNPFSiYpRj2Y9TUKJSgNr9ivLnJdjwwMRDk",
"signableEntity": {
"routeId": "basic-api",
"expirationUnixTimeStamp": 1716387600,
"rateLimiterStrategies": [
{
"$type": "CallCount",
"Count": 100
}
],
"clientAccount": "GBXDGHXCQVZUDDD4NNGBOGCXYQFKNBPIMJWW7GMUCQ2OJCPO7B3GIXWA",
"sequenceNumber": 7283947621,
"scaleOutVirtualNode": 42
}
}
}
}
Token Structure
The AccessToken has a nested structure with dual expiration controls:
-
Outer SignedEntity (AccessToken):
signingPubkey
- Client's public keysignature
- Client's signature (initially null, must be added)expirationUnixTimeStamp
- Client-supplied expiration time
-
Inner SignedEntity (GrantedAccessToken):
signingPubkey
- Server's public keysignature
- Server's signature (already present)signableEntity
- The actual GrantedAccessToken data
Dual Expiration Model
ApiCharge implements a dual expiration system for enhanced security:
- ApiCharge Subscription Expiration (1716387600) - The full duration of the purchased access
- Access Token Expiration (1716384000) - Client-supplied shorter expiration for security
The effective expiration is the earlier of these two times, allowing clients to create short-lived tokens from longer ApiCharge Subscriptions.
GrantedAccessToken Fields
- routeId - The route this token grants access to
- expirationUnixTimeStamp - When the token expires (Unix timestamp)
- rateLimiterStrategies - Quality of service parameters
- clientAccount - The client's Stellar public key
- sequenceNumber - Unique identifier for the token
- scaleOutVirtualNode - Load balancer hint (optional)
- signingPubkey - Server's public key that signed the grant
- signature - Client's signature (initially null, must be added)
Client Token Signing
Before using the access token, the client must add their own expiration time and sign the combined data with their private key.
Signing Process
- Client receives an
AccessToken
containing a server-signedGrantedAccessToken
- Client sets their desired
expirationUnixTimeStamp
(must not exceed subscription expiration) - Client creates a data structure containing both their expiration and the server's signature
- Client signs this combined data with their ED25519 private key
- Client sets their signature in the outer
AccessToken.Signature
field
Security Benefits
The client-supplied expiration provides several security advantages:
- Reduced Replay Window - Create 5-minute tokens from 1-hour ApiCharge Subscriptions
- Application-Specific Timeouts - Match token lifetime to application needs
- Session Management - Better control over user session lifecycles
- Defense in Depth - Additional security layer beyond server expiration
Using Access Tokens
Include the signed access token in API requests using one of these methods:
Method 1: apicharge Header (Recommended)
GET /api/protected-resource
apicharge: {URL-encoded-token}
Method 2: Cookie
GET /api/protected-resource
Cookie: apicharge={URL-encoded-token}
Token URL Encoding
The token must be URL-encoded before transmission. This is critical for load balancing scenarios where the scaleOutVirtualNode
is used for session affinity.
// URL encode the complete signed token
var tokenJson = JsonSerializer.Serialize(accessToken, JsonConfig.DefaultOptions);
var urlEncodedToken = HttpUtility.UrlEncode(tokenJson);
// Use in header
request.Headers.Add("apicharge", urlEncodedToken);
Token Validation Process
When ApiCharge receives a request with an access token, it performs these validation steps:
- Token Parsing - URL decode and deserialize the token
- Route Matching - Verify token is valid for the requested route
- Dual Expiration Check - Ensure both client and subscription expirations are valid
- Signature Verification - Validate both server and client signatures
- Rate Limit Enforcement - Check and update rate limiter state
Expiration Validation
ApiCharge enforces the earlier of the two expiration times:
- Client expiration:
accessToken.ExpirationUnixTimeStamp
- Subscription expiration:
accessToken.SignableEntity.SignableEntity.ExpirationUnixTimeStamp
If either expiration has passed, the token is rejected with a 402 Payment Required response.
Signature Chain Validation
The validation verifies the complete signature chain:
- Server Signature - Confirms the GrantedAccessToken was issued by the server
- Client Signature - Verifies the client signed their expiration + server signature
- Binding Check - Ensures the client public key matches the subscription owner
Route Probe Feature
Clients can discover the route ID for a path by sending a special probe request:
GET /some/api/path
apicharge: probe
This returns the route ID without requiring authentication, allowing clients to determine which route quotes they need.
Rate Limiting Integration
The authentication system automatically enforces rate limits specified in the access token:
- Call Count - Tracks API calls per token
- Data Limits - Monitors data transfer volumes
- Stream Rate - Controls bandwidth for streaming connections
- Credits - Manages flexible consumption models
Rate limiter state is maintained per token and persists across API calls and server restarts (when using Redis).
Authentication Errors
ApiCharge returns standard HTTP error codes for authentication failures:
401 Unauthorized - Missing Token
HTTP/1.1 401 Unauthorized
Content-Type: text/plain
Unauthorized
400 Bad Request - Invalid Token Format
HTTP/1.1 400 Bad Request
Content-Type: text/plain
Invalid token format
402 Payment Required - Invalid Token
HTTP/1.1 402 Payment Required
Content-Type: text/plain
Payment Required
429 Too Many Requests - Rate Limit Exceeded
HTTP/1.1 429 Too Many Requests
Content-Type: text/plain
Too Many Requests
Token Persistence and Clustering
In clustered deployments, ApiCharge uses a hybrid caching strategy for optimal rate limiting performance:
Hybrid Rate Limiter Caching
- Primary Cache - In-memory cache for fast rate limiter state access
- Backup Persistence - Redis for state durability and cross-instance sharing
- Session Affinity - Load balancer routing to maintain cache efficiency
Load Balancer Configuration
The scaleOutVirtualNode
field is critical for performance in clustered deployments. Configure your load balancer to use this value for session affinity to ensure:
- Cache Hit Efficiency - Repeated requests for the same subscription hit the same server's in-memory cache
- Accurate Rate Limiting - Rate limiter state is immediately available without Redis roundtrips
- Consistent Enforcement - Streaming connections maintain rate limit state across multiple requests
- Performance Optimization - Avoids the latency of loading rate limiter state from Redis on every request
Session Affinity Implementation
Most load balancers can be configured to route based on the scaleOutVirtualNode
value. For example:
- Nginx - Use the
hash
directive with the extracted virtual node value - HAProxy - Use
stick-table
with custom extraction logic - AWS ALB - Use target group stickiness with custom routing rules
- Azure Load Balancer - Configure session affinity based on custom headers
Fallback Behavior
When a request hits a different server (due to failover or load balancer misconfiguration):
- ApiCharge checks the local in-memory cache (cache miss)
- Loads rate limiter state from Redis
- Initializes the local cache with the Redis data
- Continues processing with potentially higher latency
While this fallback ensures correctness, optimal performance requires proper session affinity configuration.
Security Considerations
Token Security
- Always use HTTPS to prevent token interception
- Store tokens securely on the client side
- Never log or expose the full token in client applications
- Validate token signatures to prevent tampering
Clock Synchronization
Token expiration validation depends on accurate system time. Ensure clocks are synchronized within the configured tolerance (default: 2 minutes).
Key Management
- Protect client private keys used for token signing
- Use secure key storage appropriate for your platform
- Consider key rotation policies for long-running applications
Client Implementation
To implement ApiCharge authentication in your client:
- Complete the purchase flow to obtain an access token
- Sign the token with your client's private key
- URL-encode the signed token
- Include the token in the
apicharge
header or cookie - Handle authentication errors appropriately
- Monitor token expiration and renew as needed
Token Renewal
ApiCharge supports two types of token renewal:
- AccessToken Renewal - Create new short-lived tokens from the same GrantedAccessToken
- Subscription Renewal - Purchase a new GrantedAccessToken when the subscription expires
Clients should:
- Monitor both
expirationUnixTimeStamp
fields - Create new AccessTokens before the current one expires
- Initiate new purchases before the GrantedAccessToken expires
- Handle 402 Payment Required responses by triggering the appropriate renewal flow
Next Steps
- Purchase Flow - How to obtain access tokens
- Rate Limiters - Understanding quality of service controls
- Clustering - Scaling authentication across instances