CargoWise REST API Authentication & Security: Complete Guide 2025
CargoWise REST API provides modern, HTTP-based access to CargoWise functionality, but securing these integrations requires understanding authentication mechanisms, implementing proper security practices, and handling rate limits effectively. Whether you're building integrations with API keys or OAuth 2.0, security is paramount for protecting sensitive logistics data.
This comprehensive guide covers CargoWise REST API authentication methods, security best practices, rate limiting strategies, and production-ready implementation patterns. You'll learn how to implement secure authentication, manage API keys safely, handle OAuth flows, and protect your integrations from common security threats.
Understanding CargoWise REST API Authentication
Authentication Methods
CargoWise REST API supports multiple authentication methods:
1. API Key Authentication
- Simple header-based authentication
- Suitable for server-to-server integrations
- Requires secure key storage and rotation
2. OAuth 2.0
- Industry-standard authorization framework
- Supports user delegation and scoped access
- Better for applications requiring user consent
3. Basic Authentication
- Username/password based (legacy)
- Less secure, not recommended for new integrations
- May be deprecated in future versions
Security Considerations
When integrating with CargoWise REST API, consider:
- Credential Storage: Never hardcode credentials
- Transport Security: Always use HTTPS
- Token Expiration: Handle token refresh properly
- Rate Limiting: Respect API rate limits
- Error Handling: Don't expose sensitive information in errors
- Audit Logging: Log authentication events
API Key Authentication
Generating API Keys
API keys are generated in the CargoWise administration interface:
- Navigate to Administration > API Keys
- Click Create New API Key
- Configure key permissions and expiration
- Store the key securely (never commit to version control)
Implementing API Key Authentication
Use API keys in the Authorization header:
public class CargoWiseApiClient
{
private readonly HttpClient _httpClient;
private readonly string _apiKey;
private readonly ILogger<CargoWiseApiClient> _logger;
public CargoWiseApiClient(
HttpClient httpClient,
IConfiguration configuration,
ILogger<CargoWiseApiClient> logger)
{
_httpClient = httpClient;
_apiKey = configuration["CargoWise:ApiKey"]
?? throw new ArgumentNullException("CargoWise:ApiKey");
_logger = logger;
ConfigureHttpClient();
}
private void ConfigureHttpClient()
{
_httpClient.BaseAddress = new Uri("https://api.cargowise.com/");
_httpClient.DefaultRequestHeaders.Add("X-API-Key", _apiKey);
_httpClient.DefaultRequestHeaders.Add("Accept", "application/json");
_httpClient.Timeout = TimeSpan.FromSeconds(30);
}
public async Task<ShipmentResponse> GetShipmentAsync(string shipmentId)
{
var response = await _httpClient.GetAsync($"api/v1/shipments/{shipmentId}");
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<ShipmentResponse>(content);
}
}
Secure API Key Storage
Never store API keys in code or configuration files. Use secure storage:
public class SecureApiKeyProvider
{
private readonly IKeyVaultClient _keyVaultClient;
private readonly IConfiguration _configuration;
private string _cachedApiKey;
private DateTime _cacheExpiry = DateTime.MinValue;
public SecureApiKeyProvider(
IKeyVaultClient keyVaultClient,
IConfiguration configuration)
{
_keyVaultClient = keyVaultClient;
_configuration = configuration;
}
public async Task<string> GetApiKeyAsync()
{
// Check cache
if (_cachedApiKey != null && DateTime.UtcNow < _cacheExpiry)
{
return _cachedApiKey;
}
// Retrieve from Azure Key Vault
var keyVaultName = _configuration["KeyVault:Name"];
var secretName = _configuration["CargoWise:ApiKeySecretName"];
_cachedApiKey = await _keyVaultClient.GetSecretAsync(keyVaultName, secretName);
_cacheExpiry = DateTime.UtcNow.AddMinutes(30); // Cache for 30 minutes
return _cachedApiKey;
}
}
API Key Rotation
Implement key rotation for enhanced security:
public class ApiKeyRotationService
{
private readonly ICargoWiseApiClient _apiClient;
private readonly IKeyVaultClient _keyVaultClient;
private readonly ILogger<ApiKeyRotationService> _logger;
public async Task RotateApiKeyAsync()
{
try
{
// Generate new key in CargoWise
var newKey = await _apiClient.GenerateNewApiKeyAsync();
// Update in Key Vault
await _keyVaultClient.SetSecretAsync("CargoWiseApiKey", newKey);
// Test new key
await _apiClient.TestApiKeyAsync(newKey);
// Revoke old key after grace period
await Task.Delay(TimeSpan.FromHours(24)); // 24-hour grace period
await _apiClient.RevokeOldApiKeyAsync();
_logger.LogInformation("API key rotated successfully");
}
catch (Exception ex)
{
_logger.LogError(ex, "API key rotation failed");
throw;
}
}
}
OAuth 2.0 Authentication
OAuth 2.0 Flow Overview
CargoWise REST API supports OAuth 2.0 authorization code flow:
- Authorization Request: Redirect user to CargoWise authorization endpoint
- User Consent: User grants permissions
- Authorization Code: CargoWise redirects with authorization code
- Token Exchange: Exchange code for access token
- API Access: Use access token for API calls
- Token Refresh: Refresh expired tokens
Implementing OAuth 2.0 Client
public class CargoWiseOAuthClient
{
private readonly HttpClient _httpClient;
private readonly OAuthConfig _config;
private readonly ILogger<CargoWiseOAuthClient> _logger;
public CargoWiseOAuthClient(
HttpClient httpClient,
IConfiguration configuration,
ILogger<CargoWiseOAuthClient> logger)
{
_httpClient = httpClient;
_config = configuration.GetSection("CargoWise:OAuth").Get<OAuthConfig>();
_logger = logger;
}
public string GetAuthorizationUrl(string state, string redirectUri, string[] scopes)
{
var parameters = new Dictionary<string, string>
{
["client_id"] = _config.ClientId,
["response_type"] = "code",
["redirect_uri"] = redirectUri,
["scope"] = string.Join(" ", scopes),
["state"] = state
};
var queryString = string.Join("&",
parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
return $"{_config.AuthorizationEndpoint}?{queryString}";
}
public async Task<TokenResponse> ExchangeCodeForTokenAsync(
string authorizationCode,
string redirectUri)
{
var request = new HttpRequestMessage(HttpMethod.Post, _config.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "authorization_code",
["code"] = authorizationCode,
["redirect_uri"] = redirectUri,
["client_id"] = _config.ClientId,
["client_secret"] = _config.ClientSecret
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenResponse>(content);
}
public async Task<TokenResponse> RefreshTokenAsync(string refreshToken)
{
var request = new HttpRequestMessage(HttpMethod.Post, _config.TokenEndpoint)
{
Content = new FormUrlEncodedContent(new Dictionary<string, string>
{
["grant_type"] = "refresh_token",
["refresh_token"] = refreshToken,
["client_id"] = _config.ClientId,
["client_secret"] = _config.ClientSecret
})
};
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
return JsonSerializer.Deserialize<TokenResponse>(content);
}
}
Token Management
Implement secure token storage and refresh:
public class TokenManager
{
private readonly ITokenStore _tokenStore;
private readonly CargoWiseOAuthClient _oauthClient;
private readonly ILogger<TokenManager> _logger;
public TokenManager(
ITokenStore tokenStore,
CargoWiseOAuthClient oauthClient,
ILogger<TokenManager> logger)
{
_tokenStore = tokenStore;
_oauthClient = oauthClient;
_logger = logger;
}
public async Task<string> GetValidAccessTokenAsync(string userId)
{
var token = await _tokenStore.GetTokenAsync(userId);
if (token == null)
{
throw new InvalidOperationException("No token found for user");
}
// Check if token is expired
if (DateTime.UtcNow >= token.ExpiresAt.AddMinutes(-5)) // Refresh 5 minutes early
{
_logger.LogInformation("Refreshing expired token for user {UserId}", userId);
token = await RefreshTokenAsync(token);
await _tokenStore.SaveTokenAsync(userId, token);
}
return token.AccessToken;
}
private async Task<TokenInfo> RefreshTokenAsync(TokenInfo currentToken)
{
try
{
var response = await _oauthClient.RefreshTokenAsync(currentToken.RefreshToken);
return new TokenInfo
{
AccessToken = response.AccessToken,
RefreshToken = response.RefreshToken ?? currentToken.RefreshToken,
ExpiresAt = DateTime.UtcNow.AddSeconds(response.ExpiresIn)
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Token refresh failed for user");
throw new TokenRefreshException("Failed to refresh token", ex);
}
}
}
Rate Limiting
Understanding Rate Limits
CargoWise REST API enforces rate limits to ensure fair usage:
- Requests per minute: Varies by endpoint and subscription tier
- Burst limits: Short-term burst capacity
- Rate limit headers: Included in API responses
Rate Limit Headers
CargoWise includes rate limit information in response headers:
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 999
X-RateLimit-Reset: 1638360000
Retry-After: 60
Implementing Rate Limiting
public class RateLimitHandler : DelegatingHandler
{
private readonly IRateLimitStore _rateLimitStore;
private readonly ILogger<RateLimitHandler> _logger;
public RateLimitHandler(
IRateLimitStore rateLimitStore,
ILogger<RateLimitHandler> logger)
{
_rateLimitStore = rateLimitStore;
_logger = logger;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Check rate limit before sending request
var endpoint = request.RequestUri.AbsolutePath;
if (!await _rateLimitStore.CanMakeRequestAsync(endpoint))
{
var retryAfter = await _rateLimitStore.GetRetryAfterAsync(endpoint);
throw new RateLimitExceededException(
$"Rate limit exceeded for {endpoint}. Retry after {retryAfter} seconds.")
{
RetryAfter = retryAfter
};
}
var response = await base.SendAsync(request, cancellationToken);
// Update rate limit from response headers
if (response.Headers.Contains("X-RateLimit-Remaining"))
{
var remaining = int.Parse(
response.Headers.GetValues("X-RateLimit-Remaining").First());
var reset = long.Parse(
response.Headers.GetValues("X-RateLimit-Reset").First());
await _rateLimitStore.UpdateRateLimitAsync(endpoint, remaining, reset);
}
// Handle 429 Too Many Requests
if (response.StatusCode == HttpStatusCode.TooManyRequests)
{
var retryAfter = response.Headers.RetryAfter?.Delta
?? TimeSpan.FromSeconds(60);
_logger.LogWarning(
"Rate limit exceeded. Retry after {RetryAfter} seconds",
retryAfter.TotalSeconds);
throw new RateLimitExceededException("Rate limit exceeded")
{
RetryAfter = (int)retryAfter.TotalSeconds
};
}
return response;
}
}
Rate Limit Store Implementation
public interface IRateLimitStore
{
Task<bool> CanMakeRequestAsync(string endpoint);
Task<int> GetRetryAfterAsync(string endpoint);
Task UpdateRateLimitAsync(string endpoint, int remaining, long resetTimestamp);
}
public class InMemoryRateLimitStore : IRateLimitStore
{
private readonly ConcurrentDictionary<string, RateLimitInfo> _rateLimits;
private readonly int _maxRequestsPerMinute;
public InMemoryRateLimitStore(int maxRequestsPerMinute = 1000)
{
_rateLimits = new ConcurrentDictionary<string, RateLimitInfo>();
_maxRequestsPerMinute = maxRequestsPerMinute;
}
public Task<bool> CanMakeRequestAsync(string endpoint)
{
var key = GetKey(endpoint);
var now = DateTime.UtcNow;
var rateLimit = _rateLimits.GetOrAdd(key, _ => new RateLimitInfo
{
Remaining = _maxRequestsPerMinute,
ResetTime = now.AddMinutes(1)
});
// Reset if time window expired
if (now >= rateLimit.ResetTime)
{
rateLimit.Remaining = _maxRequestsPerMinute;
rateLimit.ResetTime = now.AddMinutes(1);
}
return Task.FromResult(rateLimit.Remaining > 0);
}
public Task<int> GetRetryAfterAsync(string endpoint)
{
var key = GetKey(endpoint);
if (_rateLimits.TryGetValue(key, out var rateLimit))
{
var secondsUntilReset = (int)(rateLimit.ResetTime - DateTime.UtcNow).TotalSeconds;
return Task.FromResult(Math.Max(0, secondsUntilReset));
}
return Task.FromResult(60); // Default 60 seconds
}
public Task UpdateRateLimitAsync(string endpoint, int remaining, long resetTimestamp)
{
var key = GetKey(endpoint);
_rateLimits.AddOrUpdate(key,
new RateLimitInfo
{
Remaining = remaining,
ResetTime = DateTimeOffset.FromUnixTimeSeconds(resetTimestamp).DateTime
},
(_, existing) =>
{
existing.Remaining = remaining;
existing.ResetTime = DateTimeOffset.FromUnixTimeSeconds(resetTimestamp).DateTime;
return existing;
});
return Task.CompletedTask;
}
private string GetKey(string endpoint)
{
// Extract base path for rate limiting
var parts = endpoint.Split('/');
return parts.Length > 2 ? $"{parts[1]}/{parts[2]}" : endpoint;
}
}
Security Best Practices
1. Use HTTPS Always
Never use HTTP for CargoWise API calls:
public class SecureHttpClientFactory
{
public static HttpClient CreateSecureClient()
{
var handler = new HttpClientHandler
{
SslProtocols = SslProtocols.Tls12 | SslProtocols.Tls13
};
return new HttpClient(handler)
{
BaseAddress = new Uri("https://api.cargowise.com/")
};
}
}
2. Validate Certificates
Implement certificate pinning for enhanced security:
public class CertificatePinningHandler : HttpClientHandler
{
private readonly byte[] _expectedCertificateHash;
public CertificatePinningHandler(byte[] expectedCertificateHash)
{
_expectedCertificateHash = expectedCertificateHash;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var response = await base.SendAsync(request, cancellationToken);
// Validate certificate
var cert = request.RequestUri.Host;
// Certificate validation logic here
return response;
}
}
3. Implement Request Signing
Sign requests for additional security:
public class RequestSigningHandler : DelegatingHandler
{
private readonly string _apiSecret;
public RequestSigningHandler(string apiSecret)
{
_apiSecret = apiSecret;
}
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
// Generate signature
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var method = request.Method.Method;
var path = request.RequestUri.AbsolutePath;
var body = request.Content != null
? await request.Content.ReadAsStringAsync()
: string.Empty;
var signature = GenerateSignature(method, path, body, timestamp);
request.Headers.Add("X-Timestamp", timestamp);
request.Headers.Add("X-Signature", signature);
return await base.SendAsync(request, cancellationToken);
}
private string GenerateSignature(string method, string path, string body, string timestamp)
{
var message = $"{method}{path}{body}{timestamp}";
using var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(_apiSecret));
var hash = hmac.ComputeHash(Encoding.UTF8.GetBytes(message));
return Convert.ToBase64String(hash);
}
}
4. Audit Logging
Log all authentication and API access events:
public class AuditLoggingHandler : DelegatingHandler
{
private readonly ILogger<AuditLoggingHandler> _logger;
protected override async Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request,
CancellationToken cancellationToken)
{
var startTime = DateTime.UtcNow;
var endpoint = request.RequestUri.AbsolutePath;
var method = request.Method.Method;
try
{
var response = await base.SendAsync(request, cancellationToken);
var duration = (DateTime.UtcNow - startTime).TotalMilliseconds;
_logger.LogInformation(
"API call: {Method} {Endpoint} - Status: {Status} - Duration: {Duration}ms",
method, endpoint, response.StatusCode, duration);
return response;
}
catch (Exception ex)
{
_logger.LogError(ex,
"API call failed: {Method} {Endpoint}",
method, endpoint);
throw;
}
}
}
Error Handling
Handling Authentication Errors
public class AuthenticationErrorHandler
{
public void HandleAuthenticationError(HttpResponseMessage response)
{
switch (response.StatusCode)
{
case HttpStatusCode.Unauthorized:
throw new AuthenticationException("Invalid API key or token");
case HttpStatusCode.Forbidden:
throw new AuthorizationException("Insufficient permissions");
case HttpStatusCode.TooManyRequests:
var retryAfter = response.Headers.RetryAfter?.Delta
?? TimeSpan.FromSeconds(60);
throw new RateLimitExceededException("Rate limit exceeded")
{
RetryAfter = (int)retryAfter.TotalSeconds
};
default:
response.EnsureSuccessStatusCode();
break;
}
}
}
Testing Authentication
Unit Tests
[Fact]
public async Task ShouldAuthenticateWithApiKey()
{
var client = new CargoWiseApiClient(/* ... */);
var shipment = await client.GetShipmentAsync("SH-001");
Assert.NotNull(shipment);
}
[Fact]
public async Task ShouldRefreshExpiredToken()
{
var tokenManager = new TokenManager(/* ... */);
var expiredToken = new TokenInfo
{
AccessToken = "expired-token",
RefreshToken = "refresh-token",
ExpiresAt = DateTime.UtcNow.AddMinutes(-10)
};
var newToken = await tokenManager.RefreshTokenAsync(expiredToken);
Assert.True(newToken.ExpiresAt > DateTime.UtcNow);
}
Advanced Security Patterns
Token Encryption
Encrypt tokens at rest:
public class EncryptedTokenStore : ITokenStore
{
private readonly IDataProtector _protector;
public async Task SaveTokenAsync(string userId, TokenInfo token)
{
// Encrypt sensitive token data
var encryptedToken = new EncryptedTokenInfo
{
AccessToken = _protector.Protect(token.AccessToken),
RefreshToken = _protector.Protect(token.RefreshToken),
ExpiresAt = token.ExpiresAt
};
await _storage.SaveAsync(userId, encryptedToken);
}
public async Task<TokenInfo> GetTokenAsync(string userId)
{
var encrypted = await _storage.GetAsync<EncryptedTokenInfo>(userId);
return new TokenInfo
{
AccessToken = _protector.Unprotect(encrypted.AccessToken),
RefreshToken = _protector.Unprotect(encrypted.RefreshToken),
ExpiresAt = encrypted.ExpiresAt
};
}
}
Multi-Factor Authentication
Implement MFA for API access:
public class MfaApiClient
{
private readonly ICargoWiseApiClient _apiClient;
private readonly IMfaService _mfaService;
public async Task<T> CallWithMfaAsync<T>(Func<Task<T>> apiCall)
{
// Check if MFA required
if (await _mfaService.IsMfaRequiredAsync())
{
// Request MFA code
var mfaCode = await RequestMfaCodeAsync();
// Verify MFA
var verified = await _mfaService.VerifyMfaCodeAsync(mfaCode);
if (!verified)
{
throw new MfaVerificationFailedException();
}
}
return await apiCall();
}
}
Real-World Security Scenarios
Scenario 1: Credential Compromise
Handle compromised credentials:
public class CredentialCompromiseHandler
{
public async Task HandleCompromiseAsync(string apiKey)
{
// Immediately revoke compromised key
await RevokeApiKeyAsync(apiKey);
// Generate new key
var newKey = await GenerateNewApiKeyAsync();
// Update in secure storage
await UpdateApiKeyAsync(newKey);
// Notify security team
await NotifySecurityTeamAsync(apiKey);
// Audit log the incident
await AuditLogCompromiseAsync(apiKey);
}
}
Scenario 2: Token Theft Detection
Detect and respond to token theft:
public class TokenTheftDetector
{
public async Task<bool> DetectTokenTheftAsync(string token, string clientIp)
{
var lastUsage = await GetLastTokenUsageAsync(token);
// Check for suspicious patterns
if (lastUsage != null)
{
// Different IP address
if (lastUsage.IpAddress != clientIp)
{
// Check geographic distance
var distance = CalculateDistance(lastUsage.Location, GetLocation(clientIp));
if (distance > 1000) // More than 1000km
{
await RevokeTokenAsync(token);
await AlertSecurityAsync(token, "Suspicious location change");
return true;
}
}
// Unusual access pattern
if (IsUnusualAccessPattern(lastUsage))
{
await RevokeTokenAsync(token);
return true;
}
}
return false;
}
}
Performance Optimization
Token Caching Strategy
Optimize token retrieval:
public class CachedTokenManager
{
private readonly IMemoryCache _cache;
private readonly ITokenStore _tokenStore;
public async Task<string> GetAccessTokenAsync(string userId)
{
var cacheKey = $"token:{userId}";
if (_cache.TryGetValue(cacheKey, out CachedToken cached))
{
// Check if still valid
if (cached.ExpiresAt > DateTime.UtcNow.AddMinutes(5))
{
return cached.AccessToken;
}
}
// Get from store
var token = await _tokenStore.GetTokenAsync(userId);
// Cache for remaining validity
var cacheDuration = token.ExpiresAt - DateTime.UtcNow;
_cache.Set(cacheKey, new CachedToken
{
AccessToken = token.AccessToken,
ExpiresAt = token.ExpiresAt
}, cacheDuration);
return token.AccessToken;
}
}
Connection Pooling
Optimize HTTP connections:
public class OptimizedHttpClientFactory
{
public static HttpClient CreateOptimizedClient()
{
var handler = new HttpClientHandler
{
MaxConnectionsPerServer = 100,
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30)
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30)
};
}
}
Extended FAQ
Q: How often should I rotate API keys?
A: Rotate API keys:
- Immediately if compromised
- Quarterly for high-security applications
- Annually for standard applications
- Before employee departure
Q: Should I use API keys or OAuth 2.0?
A: Use API keys for:
- Service-to-service communication
- Simple integrations
- Internal applications
Use OAuth 2.0 for:
- User-facing applications
- Third-party integrations
- Granular permissions needed
Q: How do I handle token expiration gracefully?
A: Implement automatic refresh:
public async Task<T> CallApiWithAutoRefreshAsync<T>(Func<string, Task<T>> apiCall)
{
var token = await GetValidAccessTokenAsync();
try
{
return await apiCall(token);
}
catch (UnauthorizedException)
{
// Token expired, refresh and retry
token = await RefreshTokenAsync();
return await apiCall(token);
}
}
Q: How do I secure API keys in containers?
A: Use container secrets:
# docker-compose.yml
services:
api-client:
environment:
- API_KEY_FILE=/run/secrets/api_key
secrets:
- api_key
secrets:
api_key:
external: true
Q: What's the best way to test authentication?
A: Use test doubles:
public class TestApiClient : ICargoWiseApiClient
{
public Task<ShipmentResponse> GetShipmentAsync(string id)
{
return Task.FromResult(new ShipmentResponse
{
Id = id,
Status = "Test"
});
}
}
Case Studies
Case Study 1: High-Security Integration
Challenge: A financial services company needed to integrate with CargoWise with strict security requirements.
Solution: Implemented multi-layer security:
public class HighSecurityApiClient
{
private readonly IEncryptedTokenStore _tokenStore;
private readonly IMfaService _mfaService;
private readonly IAuditLogger _auditLogger;
public async Task<T> CallSecurelyAsync<T>(Func<Task<T>> apiCall)
{
// MFA verification
await VerifyMfaAsync();
// Get encrypted token
var token = await _tokenStore.GetTokenAsync();
// Audit log
await _auditLogger.LogApiCallAsync();
// Make API call
return await apiCall();
}
}
Results:
- Zero security incidents
- Full audit trail
- Compliance with regulations
Troubleshooting Authentication Issues
Common Authentication Errors
Error 1: Invalid API Key
Symptoms:
- 401 Unauthorized responses
- Authentication failures
Solution:
public class ApiKeyValidator
{
public async Task<bool> ValidateApiKeyAsync(string apiKey)
{
// Check if key exists
var keyInfo = await GetApiKeyInfoAsync(apiKey);
if (keyInfo == null)
{
_logger.LogWarning("Invalid API key attempted: {ApiKeyPrefix}",
apiKey.Substring(0, 8));
return false;
}
// Check if key is active
if (!keyInfo.IsActive)
{
_logger.LogWarning("Inactive API key used: {KeyId}", keyInfo.Id);
return false;
}
// Check expiration
if (keyInfo.ExpiresAt < DateTime.UtcNow)
{
_logger.LogWarning("Expired API key used: {KeyId}", keyInfo.Id);
return false;
}
return true;
}
}
Error 2: Token Expiration
Symptoms:
- 401 Unauthorized after period of inactivity
- Token refresh failures
Solution:
public class TokenRefreshHandler
{
public async Task<string> GetValidTokenAsync()
{
var token = await GetStoredTokenAsync();
// Refresh if expiring soon (within 5 minutes)
if (token.ExpiresAt < DateTime.UtcNow.AddMinutes(5))
{
try
{
token = await RefreshTokenAsync(token);
await StoreTokenAsync(token);
}
catch (Exception ex)
{
_logger.LogError(ex, "Failed to refresh token");
throw;
}
}
return token.AccessToken;
}
}
Performance Optimization
Token Caching Best Practices
Optimize token retrieval with intelligent caching:
public class OptimizedTokenCache
{
private readonly IMemoryCache _cache;
private readonly SemaphoreSlim _semaphore = new(1, 1);
public async Task<string> GetTokenAsync(string userId)
{
var cacheKey = $"token:{userId}";
if (_cache.TryGetValue(cacheKey, out CachedToken cached))
{
// Check if still valid with buffer
if (cached.ExpiresAt > DateTime.UtcNow.AddMinutes(5))
{
return cached.AccessToken;
}
}
// Use semaphore to prevent concurrent refresh
await _semaphore.WaitAsync();
try
{
// Double-check after acquiring lock
if (_cache.TryGetValue(cacheKey, out cached) &&
cached.ExpiresAt > DateTime.UtcNow.AddMinutes(5))
{
return cached.AccessToken;
}
// Refresh token
var token = await RefreshTokenAsync(userId);
// Cache with expiration buffer
var cacheDuration = token.ExpiresAt - DateTime.UtcNow - TimeSpan.FromMinutes(5);
_cache.Set(cacheKey, new CachedToken
{
AccessToken = token.AccessToken,
ExpiresAt = token.ExpiresAt
}, cacheDuration);
return token.AccessToken;
}
finally
{
_semaphore.Release();
}
}
}
Connection Pooling
Optimize HTTP connections:
public class OptimizedHttpClientFactory
{
public static HttpClient CreateOptimizedClient()
{
var handler = new SocketsHttpHandler
{
PooledConnectionLifetime = TimeSpan.FromMinutes(2),
PooledConnectionIdleTimeout = TimeSpan.FromSeconds(30),
MaxConnectionsPerServer = 100
};
return new HttpClient(handler)
{
Timeout = TimeSpan.FromSeconds(30),
DefaultRequestHeaders =
{
ConnectionClose = false // Keep connections alive
}
};
}
}
Security Hardening
API Key Rotation
Implement secure key rotation:
public class ApiKeyRotationService
{
public async Task RotateApiKeyAsync(string keyId)
{
// Generate new key
var newKey = await GenerateNewApiKeyAsync();
// Create overlap period (old key valid for 24 hours)
var oldKey = await GetApiKeyAsync(keyId);
oldKey.ExpiresAt = DateTime.UtcNow.AddHours(24);
// Activate new key
await ActivateApiKeyAsync(newKey);
// Notify clients
await NotifyKeyRotationAsync(keyId, newKey);
// Schedule old key deletion
_backgroundJobClient.Schedule(() => DeleteExpiredKeyAsync(oldKey.Id),
oldKey.ExpiresAt);
}
}
Security Monitoring
Monitor for security threats:
public class SecurityMonitoringService
{
public async Task MonitorAuthenticationAsync(AuthenticationEvent evt)
{
// Track failed attempts
if (!evt.Success)
{
await RecordFailedAttemptAsync(evt);
// Check for brute force
var recentFailures = await GetRecentFailuresAsync(
evt.ClientIp, TimeSpan.FromMinutes(15));
if (recentFailures.Count > 5)
{
await BlockIpAsync(evt.ClientIp, TimeSpan.FromHours(1));
await AlertSecurityTeamAsync("Brute force detected", evt);
}
}
// Track unusual patterns
if (await IsUnusualPatternAsync(evt))
{
await AlertSecurityTeamAsync("Unusual authentication pattern", evt);
}
}
}
Extended FAQ
Q: How do I handle API key rotation without downtime?
A: Use overlapping keys:
// Support both old and new keys during rotation
public async Task<bool> ValidateApiKeyAsync(string apiKey)
{
var keyInfo = await GetApiKeyInfoAsync(apiKey);
// Check if it's an old key in rotation period
if (keyInfo.IsRotating && keyInfo.ExpiresAt > DateTime.UtcNow)
{
return true; // Still valid during rotation
}
return keyInfo.IsActive && keyInfo.ExpiresAt > DateTime.UtcNow;
}
Q: What's the best way to store API keys securely?
A: Use Azure Key Vault:
public class SecureApiKeyStore
{
private readonly SecretClient _keyVaultClient;
public async Task<string> GetApiKeyAsync(string keyName)
{
var secret = await _keyVaultClient.GetSecretAsync(keyName);
return secret.Value.Value;
}
public async Task SaveApiKeyAsync(string keyName, string apiKey)
{
await _keyVaultClient.SetSecretAsync(keyName, apiKey);
}
}
Q: How do I implement rate limiting per API key?
A: Use distributed rate limiting:
public class ApiKeyRateLimiter
{
private readonly IDistributedCache _cache;
public async Task<bool> CheckRateLimitAsync(string apiKey, int limit)
{
var key = $"ratelimit:{apiKey}";
var current = await _cache.GetStringAsync(key);
if (current == null || int.Parse(current) < limit)
{
var newCount = current == null ? 1 : int.Parse(current) + 1;
await _cache.SetStringAsync(key, newCount.ToString(),
new DistributedCacheEntryOptions
{
AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(1)
});
return true;
}
return false;
}
}
Case Studies
Case Study 2: High-Volume API Integration
Challenge: A logistics platform needed to handle 1M+ API calls per day with secure authentication.
Solution: Implemented token caching with distributed storage:
public class HighVolumeAuthService
{
private readonly IRedisCache _redisCache;
private readonly ITokenService _tokenService;
public async Task<string> GetAccessTokenAsync(string clientId)
{
// Check Redis cache first
var cacheKey = $"token:{clientId}";
var cached = await _redisCache.GetStringAsync(cacheKey);
if (cached != null)
{
return cached;
}
// Get new token
var token = await _tokenService.GetTokenAsync(clientId);
// Cache in Redis with TTL
await _redisCache.SetStringAsync(cacheKey, token.AccessToken,
TimeSpan.FromMinutes(55)); // Cache for 55 minutes (token valid 60)
return token.AccessToken;
}
}
Results:
- 95% reduction in token requests
- Sub-10ms authentication latency
- Zero authentication failures
Migration Guide
Migrating from API Keys to OAuth 2.0
Step 1: Support both authentication methods:
public class HybridAuthHandler
{
public async Task<ClaimsPrincipal> AuthenticateAsync(HttpContext context)
{
// Try OAuth 2.0 first
var oauthResult = await TryOAuth2Async(context);
if (oauthResult.Succeeded)
{
return oauthResult.Principal;
}
// Fall back to API key
var apiKeyResult = await TryApiKeyAsync(context);
if (apiKeyResult.Succeeded)
{
return apiKeyResult.Principal;
}
return null;
}
}
Step 2: Migrate clients gradually:
public class MigrationTracker
{
public async Task TrackMigrationAsync(string clientId, string authMethod)
{
await _analytics.TrackEventAsync("auth_method_used", new
{
ClientId = clientId,
Method = authMethod,
Timestamp = DateTime.UtcNow
});
}
}
Conclusion
Implementing secure authentication for CargoWise REST API requires careful attention to credential management, token handling, rate limiting, and security best practices. By following the patterns and practices outlined in this guide, you can build secure, production-ready integrations that protect sensitive logistics data.
Key Takeaways:
- Use secure credential storage - Never hardcode API keys
- Implement OAuth 2.0 properly - Handle token refresh correctly
- Respect rate limits - Implement rate limiting logic
- Use HTTPS always - Never use HTTP
- Log authentication events - Enable audit logging
- Handle errors gracefully - Don't expose sensitive information
- Encrypt sensitive data - Protect tokens at rest
- Monitor for anomalies - Detect security threats
- Rotate credentials - Regular key rotation
- Test security - Comprehensive security testing
Next Steps:
- Choose authentication method (API key or OAuth 2.0)
- Implement secure credential storage
- Add rate limiting
- Set up audit logging
- Test authentication flows
- Monitor for security threats
Best Practices Summary
Security Checklist
- Use secure credential storage (Azure Key Vault, AWS Secrets Manager)
- Implement OAuth 2.0 with proper token refresh
- Enable rate limiting per API key
- Use HTTPS for all API calls
- Implement audit logging for all authentication events
- Encrypt tokens at rest
- Monitor for authentication anomalies
- Rotate API keys regularly
- Implement MFA for sensitive operations
- Test security thoroughly
Production Readiness
Before deploying to production:
- Review all authentication flows
- Test token refresh scenarios
- Verify rate limiting works correctly
- Set up monitoring and alerting
- Document security procedures
- Train team on security practices
- Conduct security audit
Additional Security Considerations
API Key Management
Best practices for API key management:
public class ApiKeyManager
{
public async Task<string> GenerateApiKeyAsync(string clientId)
{
var key = GenerateSecureKey();
var hashed = HashKey(key);
await StoreApiKeyAsync(clientId, hashed);
await LogKeyGenerationAsync(clientId);
return key; // Return only once
}
public async Task<bool> ValidateApiKeyAsync(string apiKey)
{
var hashed = HashKey(apiKey);
var stored = await GetStoredKeyAsync(hashed);
return stored != null && stored.IsActive;
}
}
Security Audit Logging
Comprehensive security audit logging:
public class SecurityAuditLogger
{
public async Task LogAuthenticationEventAsync(AuthenticationEvent evt)
{
await _auditLogger.LogAsync(new AuditEntry
{
EventType = "Authentication",
UserId = evt.UserId,
ClientId = evt.ClientId,
Success = evt.Success,
IpAddress = evt.IpAddress,
Timestamp = DateTime.UtcNow,
Details = evt.Details
});
}
}
For more CargoWise integration guidance, explore our CargoWise REST API vs eAdapter comparison or eAdapter Integration Patterns.