A lightweight, flexible Go package for adding configurable authentication to HTTP requests. Supports multiple authentication strategies including HMAC-SHA256 signatures with built-in protection against replay attacks and query parameter tampering.
- go-httpclient
Why do you need HTTP request authentication?
In modern distributed systems and microservices architectures, securing HTTP communication between services is critical:
- Prevent Unauthorized Access: Ensure only authenticated clients can access your APIs
- Protect Against Replay Attacks: Time-based signatures prevent attackers from reusing captured requests
- Maintain Request Integrity: Cryptographic signatures detect any tampering with request data
- Secure Query Parameters: Include URL parameters in signatures to prevent manipulation
- Flexible Security Levels: Choose the right authentication strategy for your use case
Without proper authentication, your APIs are vulnerable to:
- Unauthorized access and data breaches
- Man-in-the-middle attacks
- Request tampering and parameter injection
- Replay attacks using captured requests
go-httpclient is a Go package that provides configurable HTTP authentication mechanisms for both client-side request signing and server-side request verification.
-
None Mode (
AuthModeNone)- No authentication headers added
- Use for public endpoints or when authentication is handled elsewhere
-
Simple Mode (
AuthModeSimple)- API secret key sent in a custom header
- Lightweight authentication for internal services
- Default header:
X-API-Secret(customizable)
-
HMAC Mode (
AuthModeHMAC)- HMAC-SHA256 cryptographic signatures
- Three headers: signature, timestamp, and nonce
- Includes request method, path, query parameters, and body in signature
- Built-in replay attack prevention with timestamp validation
- Default headers:
X-Signature,X-Timestamp,X-Nonce(all customizable)
-
GitHub Mode (
AuthModeGitHub)- GitHub webhook-compatible HMAC-SHA256 signatures
- Single header with "sha256=" prefix format
- Signature includes only request body (compatible with GitHub webhooks)
- Default header:
X-Hub-Signature-256(customizable) - No timestamp validation (use HTTPS and regular secret rotation)
- Automatic Authentication: One-line client creation with built-in request signing via
NewAuthClient - Flexible Configuration: Option Pattern for easy customization without breaking changes
- Client options:
WithTimeout,WithMaxBodySize,WithSkipAuthFunc,WithHMACHeaders, etc. - Verification options:
WithVerifyMaxAge,WithVerifyMaxBodySize
- Client options:
- Custom TLS Certificates: Load certificates from files, URLs, or embedded content for enterprise PKI
- DoS Protection: Configurable body size limits prevent memory exhaustion attacks (default: 10MB)
- Zero Dependencies (except
google/uuidfor nonce generation) - Simple API: Easy to integrate into existing HTTP clients
- Dual Purpose: Works for both client-side signing and server-side verification
- Customizable: Override default header names to match your API standards
- Production Ready: 90%+ test coverage, comprehensive linting, and security scanning
- Well Tested: 1200+ lines of test code covering all scenarios
go get github.com/appleboy/go-httpclientOption 1: Simple HTTP Client (No Authentication)
package main
import (
"fmt"
"log"
"github.com/appleboy/go-httpclient"
)
func main() {
// Create a simple HTTP client
client, err := httpclient.NewClient()
if err != nil {
log.Fatal(err)
}
// Make requests - just like http.Client!
resp, err := client.Get("https://api.example.com/public")
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Println("Request sent successfully")
}Option 2: Authenticated HTTP Client
package main
import (
"bytes"
"fmt"
"log"
"github.com/appleboy/go-httpclient"
)
func main() {
// Create authenticated HTTP client
client, err := httpclient.NewAuthClient(httpclient.AuthModeHMAC, "your-secret-key")
if err != nil {
log.Fatal(err)
}
// Send request - authentication headers added automatically!
body := []byte(`{"user": "john"}`)
resp, err := client.Post(
"https://api.example.com/users",
"application/json",
bytes.NewReader(body),
)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
fmt.Println("Request sent with automatic HMAC authentication")
}For complete, runnable examples, see the _example directory. Each example includes detailed documentation and can be run independently.
For public endpoints or when authentication is not required, use the convenient NewClient() function:
// Recommended: Use NewClient() for clarity
client, err := httpclient.NewClient()
if err != nil {
log.Fatal(err)
}
resp, err := client.Get("https://api.example.com/public")
// No authentication headers added
// With custom configuration
client, err := httpclient.NewClient(
httpclient.WithTimeout(10*time.Second),
httpclient.WithTLSCertFromFile("/etc/ssl/certs/ca.crt"),
)
// Alternative: Use NewAuthClient with AuthModeNone
client, err := httpclient.NewAuthClient(httpclient.AuthModeNone, "")For basic authentication with a shared secret:
// Create client with simple authentication (default header: X-API-Secret)
client := httpclient.NewAuthClient(httpclient.AuthModeSimple, "my-secret-key")
// Make requests - authentication is automatic
resp, err := client.Get("https://api.example.com/data")
// Request will automatically include:
// X-API-Secret: my-secret-keySee full example: _example/01-simple-auth
For cryptographically secure request signing:
// Create client with HMAC authentication
client := httpclient.NewAuthClient(httpclient.AuthModeHMAC, "shared-secret")
// Make requests - authentication is automatic
body := []byte(`{"action": "transfer", "amount": 100}`)
resp, err := client.Post(
"https://api.example.com/transactions?user=123",
"application/json",
bytes.NewReader(body),
)
// Request will automatically include:
// X-Signature: a3c8f9b2... (HMAC-SHA256 signature)
// X-Timestamp: 1704067200 (Unix timestamp)
// X-Nonce: 550e8400-e29b-41d4-a716-446655440000 (UUID v4)The signature is calculated as:
HMAC-SHA256(secret, timestamp + method + path + query + body)For example:
message = "1704067200POST/transactions?user=123{\"action\":\"transfer\",\"amount\":100}"
signature = HMAC-SHA256("shared-secret", message)See full example: _example/02-hmac-auth
GitHub mode provides compatibility with GitHub webhook signature format and other webhook providers using similar HMAC-SHA256 patterns:
// Create client with GitHub-style authentication
client := httpclient.NewAuthClient(httpclient.AuthModeGitHub, "webhook-secret")
// Make requests - authentication is automatic
payload := []byte(`{"action":"opened","pull_request":{"id":123}}`)
resp, err := client.Post(
"https://api.example.com/webhook",
"application/json",
bytes.NewReader(payload),
)
// Request will automatically include:
// X-Hub-Signature-256: sha256=a3c8f9b2... (GitHub-style signature)Server-side verification:
func webhookHandler(w http.ResponseWriter, r *http.Request) {
// Create auth config with GitHub mode
auth := httpclient.NewAuthConfig(httpclient.AuthModeGitHub, "webhook-secret")
// Verify signature
if err := auth.Verify(r); err != nil {
http.Error(w, "Invalid signature", http.StatusUnauthorized)
return
}
// Body is preserved after verification
body, _ := io.ReadAll(r.Body)
var payload map[string]interface{}
json.Unmarshal(body, &payload)
log.Printf("Received webhook: %v", payload)
w.WriteHeader(http.StatusOK)
}Key differences from HMAC mode:
| Feature | HMAC Mode | GitHub Mode |
|---|---|---|
| Signature content | timestamp + method + path + query + body | body only |
| Signature format | hex digest (64 chars) | "sha256=" + hex digest (71 chars) |
| Headers | 3 (X-Signature, X-Timestamp, X-Nonce) | 1 (X-Hub-Signature-256) |
| Timestamp validation | ✅ Yes (±5 minutes) | ❌ No |
| Replay attack protection | ✅ Yes |
Security notes:
- GitHub mode does NOT validate timestamps (GitHub webhooks don't provide them)
- Always use HTTPS to protect the secret in transit
- Regularly rotate webhook secrets to mitigate replay attack risks
- Default body size limit (10MB) protects against DoS attacks
Override default header names to match your API standards:
// Simple mode with custom header name
client := httpclient.NewAuthClient(
httpclient.AuthModeSimple,
"my-key",
httpclient.WithHeaderName("Authorization"),
)
// Requests will include:
// Authorization: my-keyFor HMAC mode:
// HMAC mode with custom header names
client := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithHMACHeaders("X-Custom-Signature", "X-Request-Time", "X-Request-ID"),
)
// Requests will include custom header namesSee full example: _example/03-custom-headers
Verify authentication on the server side with the unified Verify() method:
func authMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Create auth config with same mode and secret as client
auth := httpclient.NewAuthConfig(httpclient.AuthModeHMAC, "shared-secret")
// Verify automatically selects the right verification based on mode
// Use defaults (5 minutes max age, 10MB max body size)
if err := auth.Verify(r); err != nil {
http.Error(w, "Authentication failed: "+err.Error(), http.StatusUnauthorized)
return
}
// Authentication is valid, proceed to next handler
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/api/secure", func(w http.ResponseWriter, r *http.Request) {
// Your secure handler code
w.Write([]byte("Access granted"))
})
// Wrap with authentication middleware
http.ListenAndServe(":8080", authMiddleware(mux))
}Customize Verification Options (HMAC mode only):
// Strict API endpoint - 2 minute timeout, 1MB limit
if err := auth.Verify(r,
httpclient.WithVerifyMaxAge(2*time.Minute),
httpclient.WithVerifyMaxBodySize(1*1024*1024),
); err != nil {
return err
}
// File upload endpoint - 10 minute timeout, 50MB limit
if err := auth.Verify(r,
httpclient.WithVerifyMaxAge(10*time.Minute),
httpclient.WithVerifyMaxBodySize(50*1024*1024),
); err != nil {
return err
}
// Custom max age only (use default 10MB body limit)
if err := auth.Verify(r,
httpclient.WithVerifyMaxAge(15*time.Minute),
); err != nil {
return err
}Available Verification Options:
WithVerifyMaxAge(duration)- Maximum age for request timestamps (default: 5 minutes)WithVerifyMaxBodySize(bytes)- Maximum request body size to prevent DoS attacks (default: 10MB)
See full example: _example/04-server-verification
The simplest way to use this package - create an HTTP client that automatically signs all requests:
// Create authenticated client with automatic signing
client, err := httpclient.NewAuthClient(httpclient.AuthModeHMAC, "secret")
if err != nil {
log.Fatal(err)
}
// Use it like a normal http.Client - authentication is automatic!
resp, err := client.Post(
"https://api.example.com/data",
"application/json",
bytes.NewReader([]byte(`{"key": "value"}`)),
)With Configuration Options:
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTimeout(10*time.Second),
httpclient.WithMaxBodySize(5*1024*1024), // 5MB limit
httpclient.WithSkipAuthFunc(func(req *http.Request) bool {
// Skip authentication for health checks
return strings.HasPrefix(req.URL.Path, "/health")
}),
)
if err != nil {
log.Fatal(err)
}Available Options:
WithTimeout(duration)- Set request timeout (default: 30s)WithMaxBodySize(bytes)- Limit request body size (default: 10MB)WithTransport(transport)- Use custom HTTP transport (must be*http.Transportif using TLS options)WithSkipAuthFunc(func)- Conditionally skip authenticationWithHMACHeaders(sig, ts, nonce)- Custom HMAC header namesWithHeaderName(name)- Custom header for simple modeWithRequestID(func)- Generate unique request IDs for tracingWithRequestIDHeader(name)- Custom header for request ID (default: "X-Request-ID")WithTLSCertFromFile(path)- Load TLS certificate from fileWithTLSCertFromURL(ctx, url)- Download TLS certificate from URL (with context for timeout control)WithTLSCertFromBytes(pem)- Load TLS certificate from bytesWithInsecureSkipVerify(bool)- Skip TLS certificate verification (testing only)WithMTLSFromFile(certPath, keyPath)- Load mTLS client certificate from filesWithMTLSFromBytes(certPEM, keyPEM)- Load mTLS client certificate from bytes
Important: TLS options (WithTLSCert*, WithMTLS*, WithInsecureSkipVerify) cannot be combined with non-*http.Transport RoundTrippers. See Transport Chaining with TLS for details.
See full examples:
_example/05-roundtripper-client- Basic automatic authentication_example/06-options-showcase- All configuration options_example/07-transport-chaining- Advanced transport composition
Add unique request IDs to every request for distributed tracing, logging correlation, and debugging. Request IDs help you track requests across microservices and correlate client-side and server-side logs.
Basic Usage with UUID:
import "github.com/google/uuid"
// Create client with automatic request ID generation
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithRequestID(func() string {
return uuid.New().String()
}),
)
if err != nil {
log.Fatal(err)
}
// Every request automatically includes X-Request-ID header
resp, err := client.Get("https://api.example.com/data")
// Request includes: X-Request-ID: 550e8400-e29b-41d4-a716-446655440000Custom Request ID Format:
var requestCounter int
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithRequestID(func() string {
requestCounter++
return fmt.Sprintf("req-%d-%d", time.Now().Unix(), requestCounter)
}),
)
// Request includes: X-Request-ID: req-1704067200-1Custom Header Name:
// Use X-Correlation-ID instead of X-Request-ID
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithRequestID(uuid.New().String),
httpclient.WithRequestIDHeader("X-Correlation-ID"),
)
// Request includes: X-Correlation-ID: 550e8400-e29b-41d4-a716-446655440000Preserving User-Provided IDs:
If a request already has a request ID header, it will be preserved:
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithRequestID(uuid.New().String),
)
// User-provided ID takes precedence
req, _ := http.NewRequest(http.MethodGet, "https://api.example.com/data", nil)
req.Header.Set("X-Request-ID", "user-custom-id-123")
resp, err := client.Do(req)
// Request includes: X-Request-ID: user-custom-id-123 (preserved)Works with All Features:
Request ID tracking integrates seamlessly with all authentication modes and features:
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithRequestID(uuid.New().String),
httpclient.WithTimeout(10*time.Second),
httpclient.WithSkipAuthFunc(func(req *http.Request) bool {
return strings.HasPrefix(req.URL.Path, "/health")
}),
)
// Request ID is added to ALL requests (even when auth is skipped)Key Features:
- Automatic Generation: Unique ID for every request
- Preserves User IDs: User-provided IDs are never overwritten
- Customizable: Custom generator functions and header names
- Always Applied: Added before authentication headers (tracks all requests)
- Zero Overhead: No performance impact when not configured
Use Cases:
- Distributed Tracing: Track requests across microservices
- Log Correlation: Match client and server logs
- Debugging: Identify specific request flows
- Monitoring: Track request latency and errors
- Support: Customers can provide request ID for troubleshooting
See full example: _example/10-request-id-tracking
For enterprise environments with custom Certificate Authorities or self-signed certificates:
// Load certificate from file
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTLSCertFromFile("/etc/ssl/certs/company-ca.crt"),
)
if err != nil {
log.Fatal(err)
}
// Load certificate from URL with timeout control
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTLSCertFromURL(ctx, "https://internal-ca.company.com/ca.crt"),
)
if err != nil {
log.Fatal(err)
}
// Load certificate from embedded content
certPEM := []byte(`-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJAKL0UG+mRKmdMA0GCSqGSIb3DQEBCwUAMEUx...
-----END CERTIFICATE-----`)
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTLSCertFromBytes(certPEM),
)
if err != nil {
log.Fatal(err)
}
// Load multiple certificates for certificate chain
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTLSCertFromFile("/etc/ssl/certs/root-ca.crt"),
httpclient.WithTLSCertFromFile("/etc/ssl/certs/intermediate-ca.crt"),
)
if err != nil {
log.Fatal(err)
}
// Skip TLS verification (testing/development only - NOT RECOMMENDED for production)
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithInsecureSkipVerify(true),
)
if err != nil {
log.Fatal(err)
}Key Features:
- Load certificates from files, URLs, or byte content
- Multiple certificates supported for chain verification
- System certificate pool preserved (custom certs added)
- TLS 1.2+ enforced for security
- 1MB size limit prevents memory exhaustion attacks
- Configuration errors cause immediate panic for fail-fast behavior
- Skip TLS verification for testing (use with caution)
See full example: _example/08-custom-cert
Mutual TLS provides two-way authentication where both client and server verify each other's identity using certificates. This is commonly used in service-to-service communication and zero-trust architectures.
// Load mTLS certificate from files
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithMTLSFromFile("/path/to/client.crt", "/path/to/client.key"),
)
if err != nil {
log.Fatal(err)
}
// Load mTLS certificate from byte content
certPEM, _ := os.ReadFile("client.crt")
keyPEM, _ := os.ReadFile("client.key")
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithMTLSFromBytes(certPEM, keyPEM),
)
if err != nil {
log.Fatal(err)
}
// Combine mTLS with custom CA certificate
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTLSCertFromFile("/path/to/ca.crt"), // Trust server's CA
httpclient.WithMTLSFromFile("/path/to/client.crt", "/path/to/client.key"), // Client cert
)
if err != nil {
log.Fatal(err)
}Key Features:
- Load client certificates from files or byte content
- Automatic certificate validation (cert/key pair must match)
- Combine with custom CA certificates for complete mTLS setup
- Error reporting for invalid certificates or missing files
- Compatible with all other client options
See full example: _example/09-mtls
When using custom middleware (logging, metrics, tracing, etc.) with TLS options, you must provide an *http.Transport, not any other RoundTripper implementation.
Why this limitation exists:
TLS configuration (custom CA certificates, mTLS, InsecureSkipVerify) can only be applied to *http.Transport. Non-Transport RoundTrippers (like logging or metrics middleware) cannot have TLS settings directly applied to them.
What happens if you violate this:
// ❌ This will return an error
loggingMiddleware := &MyLoggingRoundTripper{
Next: http.DefaultTransport,
}
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTransport(loggingMiddleware), // Non-Transport RoundTripper
httpclient.WithTLSCertFromFile("/path/to/ca.crt"), // TLS option
)
// Error: "TLS options (WithTLSCert*, WithMTLS*, WithInsecureSkipVerify) cannot be combined
// with non-Transport RoundTrippers provided via WithTransport()..."Solution 1: Configure TLS in your Transport (Recommended)
// ✅ Configure TLS in *http.Transport, then wrap with middleware
certPool := x509.NewCertPool()
certPEM, _ := os.ReadFile("/path/to/ca.crt")
certPool.AppendCertsFromPEM(certPEM)
tlsTransport := &http.Transport{
TLSClientConfig: &tls.Config{
RootCAs: certPool,
},
}
// Pass the Transport with TLS configured
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTransport(tlsTransport),
)
// Then wrap the client's Transport with your middleware
client.Transport = &LoggingRoundTripper{
Next: client.Transport, // Wraps authRoundTripper -> tlsTransport
}Solution 2: Use *http.Transport only
// ✅ Use *http.Transport with TLS options
customTransport := &http.Transport{
MaxIdleConns: 100,
IdleConnTimeout: 90 * time.Second,
}
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTransport(customTransport), // *http.Transport is OK
httpclient.WithTLSCertFromFile("/path/to/ca.crt"), // TLS options work
)Key Points:
- TLS options work with
*http.Transportor no custom transport - TLS options cannot be combined with non-Transport RoundTrippers
- You can wrap the client's Transport with middleware after creation
- The library provides clear error messages to guide you
See full example: _example/07-transport-chaining
- Automatic Authentication: RoundTripper-based client signs requests automatically
- Flexible Configuration: Option Pattern for easy customization (timeout, body limits, etc.)
- Client Options:
WithTimeout,WithMaxBodySize,WithTransport,WithSkipAuthFunc,WithRequestID, etc. - Verification Options:
WithVerifyMaxAge,WithVerifyMaxBodySizefor per-endpoint control
- Client Options:
- Multiple Authentication Strategies: Choose between none, simple, or HMAC modes
- Request ID Tracking: Generate unique IDs for distributed tracing and log correlation
- Automatic generation with custom functions (UUID, timestamp, etc.)
- Preserves user-provided request IDs
- Customizable header names (default: X-Request-ID)
- Works with all authentication modes
- Custom TLS Certificates: Load certificates from files, URLs, or embedded content
- mTLS Support: Client certificate authentication for mutual TLS
- Enterprise PKI Support: Trust custom Certificate Authorities and self-signed certificates
- Cryptographic Security: HMAC-SHA256 signatures with constant-time comparison
- Replay Attack Protection: Timestamp validation prevents reuse of old requests
- Query Parameter Security: Include URL parameters in signature to prevent tampering
- Request Integrity: Signature covers method, path, query, and body
- Body Preservation: Request body is restored after verification for downstream handlers
- Transport Chaining: Compatible with logging, metrics, and custom transports
- Conditional Authentication: Skip auth for specific endpoints (e.g., health checks)
- DoS Protection: Configurable body size limits prevent memory exhaustion attacks
- Default: 10MB limit for verification
- Per-endpoint customization: strict APIs (1MB) vs. file uploads (50MB)
- Early rejection with
io.LimitReaderfor safe, bounded reading
- Customizable Headers: Override default header names to match your API conventions
- Dual Purpose: Same package for client signing and server verification
- Zero Config Defaults: Sensible defaults with optional customization
- Production Ready: 90%+ test coverage, comprehensive linting, and security scanning
The HMAC signature includes all critical request components:
message = timestamp + method + path + query + body
signature = HMAC-SHA256(secret, message)Example:
Request: POST /api/users?role=admin
Body: {"name": "John"}
Timestamp: 1704067200
Secret: my-secret
Message: "1704067200POST/api/users?role=admin{\"name\":\"John\"}"
Signature: HMAC-SHA256("my-secret", message)-
Replay Attack Prevention
- Each request includes a timestamp
- Server validates timestamp is within acceptable window (default: 5 minutes)
- Old signatures cannot be reused
-
Request Tampering Detection
- Any modification to method, path, query, or body invalidates signature
- Cryptographic verification ensures request integrity
-
Query Parameter Security
- Query parameters are included in signature calculation
- Prevents attackers from adding/modifying/removing parameters
-
Constant-Time Comparison
- Uses
hmac.Equal()to prevent timing attacks - Secure against side-channel attacks
- Uses
-
Memory Exhaustion Protection (DoS Prevention)
- Request Body Size Limits: Configurable per-endpoint (default: 10MB)
- Prevents attackers from sending massive payloads
- Uses
io.LimitReaderfor safe, bounded reading - Returns clear error message when limit exceeded
- TLS Certificate Size Limits: Maximum 1MB for certificate files
- Fail-Fast Validation: Early rejection before processing malicious requests
Example Configuration:
// Strict API: 1MB limit auth.Verify(req, httpclient.WithVerifyMaxBodySize(1*1024*1024)) // File upload API: 50MB limit auth.Verify(req, httpclient.WithVerifyMaxBodySize(50*1024*1024))
- Request Body Size Limits: Configurable per-endpoint (default: 10MB)
- Use HTTPS (TLS) for all requests to protect secrets in transit
- Rotate shared secrets regularly
- Use strong, random secrets (minimum 32 bytes)
- Set appropriate timestamp validation windows (default 5 minutes)
- Monitor and log authentication failures
- Use HMAC mode for production environments
Main configuration struct for authentication:
type AuthConfig struct {
Mode string // "none", "simple", "hmac" or "github"
Secret string // Shared secret key
HeaderName string // Custom header for simple mode (default: "X-API-Secret")
SignatureHeader string // Signature header for HMAC (default: "X-Signature")
TimestampHeader string // Timestamp header for HMAC (default: "X-Timestamp")
NonceHeader string // Nonce header for HMAC (default: "X-Nonce")
}Authentication Modes:
const (
AuthModeNone = "none" // No authentication
AuthModeSimple = "simple" // Simple API secret in header
AuthModeHMAC = "hmac" // HMAC-SHA256 signature
AuthModeGitHub = "github" // GitHub webhook-style HMAC-SHA256
)Default Header Names:
const (
DefaultAPISecretHeader = "X-API-Secret" // Simple mode
DefaultSignatureHeader = "X-Signature" // HMAC mode
DefaultTimestampHeader = "X-Timestamp" // HMAC mode
DefaultNonceHeader = "X-Nonce" // HMAC mode
DefaultGitHubSignatureHeader = "X-Hub-Signature-256" // GitHub mode
DefaultRequestIDHeader = "X-Request-ID" // Request tracking
)func NewClient(opts ...ClientOption) (*http.Client, error)Creates a standard HTTP client without authentication. This is a convenience wrapper around NewAuthClient with AuthModeNone.
Parameters:
opts: Optional configuration (timeout, TLS certs, request ID, etc.)
Returns: Configured *http.Client without authentication, or error if any option fails
Example:
// Basic usage
client, err := httpclient.NewClient()
if err != nil {
log.Fatal(err)
}
// With custom configuration
client, err := httpclient.NewClient(
httpclient.WithTimeout(10*time.Second),
httpclient.WithTLSCertFromFile("/etc/ssl/certs/ca.crt"),
)func NewAuthClient(mode, secret string, opts ...ClientOption) (*http.Client, error)Creates an HTTP client with automatic authentication. All requests are signed automatically based on the configured mode.
Parameters:
mode: Authentication mode (AuthModeNone,AuthModeSimple,AuthModeHMAC, orAuthModeGitHub)secret: Shared secret keyopts: Optional configuration (timeout, body limits, custom headers, etc.)
Returns: Configured *http.Client with automatic authentication, or error if any option fails
Example:
client, err := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTimeout(10*time.Second),
httpclient.WithMaxBodySize(5*1024*1024),
)
if err != nil {
log.Fatal(err)
}Configure NewAuthClient behavior:
General Options:
WithTimeout(duration)- Request timeout (default: 30s)WithMaxBodySize(bytes)- Max body size (default: 10MB, set 0 for unlimited)WithTransport(transport)- Custom base transport (must be*http.Transportif using TLS options)WithSkipAuthFunc(func(*http.Request) bool)- Skip auth conditionally
Authentication Options:
WithHMACHeaders(sig, ts, nonce string)- Custom HMAC header namesWithHeaderName(name string)- Custom header for simple mode
Request Tracking Options:
WithRequestID(func() string)- Generate unique request IDs for tracingWithRequestIDHeader(name string)- Custom header name for request ID (default: "X-Request-ID")
TLS Certificate Options:
WithTLSCertFromFile(path string)- Load certificate from file pathWithTLSCertFromURL(ctx context.Context, url string)- Download certificate from URL with context for timeout controlWithTLSCertFromBytes(certPEM []byte)- Load certificate from byte contentWithInsecureSkipVerify(skip bool)- Skip TLS certificate verification (WARNING: Use only for testing/development, never in production)
Example:
client := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithTimeout(30*time.Second),
httpclient.WithTLSCertFromFile("/etc/ssl/certs/company-ca.crt"),
)
// For testing with self-signed certificates (NOT for production)
testClient := httpclient.NewAuthClient(
httpclient.AuthModeHMAC,
"secret",
httpclient.WithInsecureSkipVerify(true),
)func NewAuthConfig(mode, secret string) *AuthConfigCreates a new AuthConfig with default header names. This is primarily used for server-side verification with the Verify() method.
Parameters:
mode: Authentication mode (AuthModeNone,AuthModeSimple, orAuthModeHMAC)secret: Shared secret key (must match the client's secret)
Returns: Configured *AuthConfig with defaults
Example:
// Server-side verification
auth := httpclient.NewAuthConfig(httpclient.AuthModeHMAC, "shared-secret")
if err := auth.Verify(req); err != nil {
http.Error(w, "Authentication failed", http.StatusUnauthorized)
return
}func (c *AuthConfig) Verify(req *http.Request, opts ...VerifyOption) errorVerifies authentication from an HTTP request (server-side validation). This is the unified verification method that automatically selects the appropriate verification logic based on the configured authentication mode.
Supported Modes:
AuthModeNone: No verification performed, returns nil immediatelyAuthModeSimple: Verifies the API secret in the configured headerAuthModeHMAC: Verifies HMAC signature with timestamp and body validation
Parameters:
req: HTTP request to verifyopts: Optional configuration (only applies to HMAC mode)
Returns: Error if verification fails or signature is invalid
Available Options (HMAC mode only):
WithVerifyMaxAge(duration)- Maximum age for request timestamps (default: 5 minutes)WithVerifyMaxBodySize(bytes)- Maximum request body size to prevent DoS attacks (default: 10MB)
Examples:
// Simple mode verification
auth := httpclient.NewAuthConfig(httpclient.AuthModeSimple, "secret")
err := auth.Verify(req)
// HMAC mode with defaults (5 minutes, 10MB)
auth := httpclient.NewAuthConfig(httpclient.AuthModeHMAC, "secret")
err := auth.Verify(req)
// HMAC mode with custom max age only
err := auth.Verify(req,
httpclient.WithVerifyMaxAge(10*time.Minute),
)
// HMAC mode with custom body size limit only
err := auth.Verify(req,
httpclient.WithVerifyMaxBodySize(5*1024*1024),
)
// HMAC mode with multiple options
err := auth.Verify(req,
httpclient.WithVerifyMaxAge(10*time.Minute),
httpclient.WithVerifyMaxBodySize(50*1024*1024),
)Security Features (HMAC mode):
- Body Size Limit: Prevents memory exhaustion DoS attacks (default: 10MB)
- Timestamp Validation: Rejects requests older than max age (default: 5 minutes)
- Clock Skew Protection: Rejects requests with future timestamps beyond max age
- Constant-Time Comparison: Uses
hmac.Equal()to prevent timing attacks - Body Preservation: Request body is restored for subsequent handlers
Run tests with coverage:
make testRun linting and formatting:
make lint
make fmtThe package includes comprehensive tests covering:
- All three authentication modes
- Custom header names
- Signature calculation consistency
- Server-side verification
- Invalid signature rejection
- Timestamp expiration
- Missing header validation
- Body preservation after verification
- Query parameter tampering prevention
- Body size limit protection (new)
- Requests within limit accepted
- Requests exceeding limit rejected
- Exact limit boundary testing
- Default 10MB limit validation
- Custom size limit configuration
- Multiple options combination
- TLS certificate loading from files, URLs, and bytes
- Multiple certificate chain verification
- Certificate error handling
- Options Pattern API usage
Coverage: 90.1% of statements
View coverage report:
go test -coverprofile=coverage.txt
go tool cover -html=coverage.txt- Go 1.24 or higher
- Make (optional, for convenience)
.
├── auth.go # Core authentication implementation
├── auth_test.go # Authentication tests (560+ lines)
├── client.go # RoundTripper-based HTTP client
├── client_test.go # Client tests (660+ lines)
├── cert_test.go # TLS certificate tests (360+ lines)
├── go.mod # Module definition
├── Makefile # Build automation
├── .golangci.yml # Linting configuration
├── _example/ # Runnable examples
│ ├── 01-simple-auth/ # Simple API key authentication
│ ├── 02-hmac-auth/ # HMAC signature authentication
│ ├── 03-custom-headers/ # Custom header names
│ ├── 04-server-verification/ # Server-side verification
│ ├── 05-roundtripper-client/ # Automatic authentication
│ ├── 06-options-showcase/ # Configuration options
│ ├── 07-transport-chaining/ # Transport composition
│ ├── 08-custom-cert/ # Custom TLS certificates
│ ├── 09-mtls/ # Mutual TLS authentication
│ └── 10-request-id-tracking/ # Request ID tracking and tracing
└── .github/workflows/ # CI/CD pipelines
├── testing.yml # Multi-platform testing
├── security.yml # Trivy security scanning
└── codeql.yml # Code quality analysismake test # Run tests with coverage
make fmt # Format code with golangci-lint
make lint # Run linting checks
make clean # Remove coverage artifacts
make help # Show all commands- Testing: Runs on Go 1.24 and 1.25, on Ubuntu and macOS
- Security: Daily Trivy scans for vulnerabilities
- Code Quality: CodeQL analysis for Go best practices
- Coverage: Automatic upload to Codecov
Contributions are welcome! Please follow these guidelines:
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature) - Make your changes
- Run tests and linting (
make test lint) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
- Follow Go best practices and idioms
- Maintain test coverage above 80%
- Pass all linting checks (golangci-lint)
- Add tests for new features
- Update documentation as needed
This project is licensed under the MIT License - see the LICENSE file for details.
Copyright (c) 2026 Bo-Yi Wu
- GitHub: @appleboy
- Website: https://blog.wu-boy.com
Support this project: