Request Signature Authentication
XyPriss provides a powerful request signature authentication system that allows you to secure your APIs using cryptographic signatures. This method is commonly used for securing webhooks, API-to-API communications, and protecting against unauthorized access.
Overview
Request Signature Authentication works by requiring clients to include a cryptographic signature in the XP-Request-Sig header. The signature is computed using a shared secret and the request data, ensuring that:
- Only authorized clients can access your API
- Requests cannot be tampered with in transit
- Replay attacks are prevented (with proper timestamp validation)
Quick Start
import { createServer } from "xypriss";
const server = createServer({
security: {
requestSignature: {
secret: "your-super-secret-api-key-12345",
debug: true, // Enable debug logging in development
},
},
});
server.get("/api/protected", (req, res) => {
res.json({ message: "Access granted!", user: req.user });
});
server.start();
Client Usage
JavaScript/Node.js Client
const crypto = require("crypto");
function signRequest(url, method, body = "", secret, timestamp = Date.now()) {
const data = `${method.toUpperCase()}${url}${body}${timestamp}`;
const signature = crypto
.createHmac("sha256", secret)
.update(data)
.digest("hex");
return { signature, timestamp };
}
// Usage
const { signature, timestamp } = signRequest(
"/api/protected",
"GET",
"",
"your-secret"
);
const response = await fetch("http://localhost:3000/api/protected", {
headers: {
"XP-Request-Sig": signature,
"X-Timestamp": timestamp,
},
});
cURL Example
# Generate signature (simplified example)
TIMESTAMP=$(date +%s)
DATA="GET/api/protected${TIMESTAMP}"
SIGNATURE=$(echo -n "$DATA" | openssl dgst -sha256 -hmac "your-secret" | cut -d' ' -f2)
curl -X GET "http://localhost:3000/api/protected" \
-H "XP-Request-Sig: $SIGNATURE" \
-H "X-Timestamp: $TIMESTAMP"
Python Client
import hmac
import hashlib
import time
import requests
def sign_request(url, method, body='', secret='', timestamp=None):
if timestamp is None:
timestamp = int(time.time() * 1000)
data = f"{method.upper()}{url}{body}{timestamp}"
signature = hmac.new(
secret.encode(),
data.encode(),
hashlib.sha256
).hexdigest()
return signature, timestamp
# Usage
signature, timestamp = sign_request('/api/protected', 'GET', '', 'your-secret')
response = requests.get('http://localhost:3000/api/protected', headers={
'XP-Request-Sig': signature,
'X-Timestamp': str(timestamp)
})
Configuration Options
interface RequestSignatureConfig {
/** Secret key for signing requests */
secret: string;
/** Enable debug logging */
debug?: boolean;
/** Algorithm to use for signing (default: sha256) */
algorithm?: "sha256" | "sha512";
/** Maximum age of signature in milliseconds (default: 300000 = 5 minutes) */
maxAge?: number;
/** Custom header name for signature (default: XP-Request-Sig) */
headerName?: string;
/** Custom header name for timestamp (default: X-Timestamp) */
timestampHeaderName?: string;
/** Whether to include body in signature (default: true) */
includeBody?: boolean;
/** Whether to include query parameters in signature (default: true) */
includeQuery?: boolean;
/** Clock skew tolerance in milliseconds (default: 30000 = 30 seconds) */
clockSkew?: number;
}
Advanced Configuration
Custom Headers and Algorithms
const server = createServer({
security: {
requestSignature: {
secret: "my-secret-key",
algorithm: "sha512", // Use SHA-512 instead of SHA-256
headerName: "X-API-Signature", // Custom header name
timestampHeaderName: "X-API-Timestamp", // Custom timestamp header
maxAge: 600000, // 10 minutes validity
clockSkew: 60000, // 1 minute clock skew tolerance
},
},
});
Selective Body Inclusion
const server = createServer({
security: {
requestSignature: {
secret: "my-secret",
includeBody: false, // Don't include body in signature (useful for file uploads)
includeQuery: true, // Include query parameters
},
},
});
Route-Specific Configuration
You can apply request signature authentication to specific routes:
const server = createServer({
security: {
requestSignature: {
secret: "global-secret",
},
routeConfig: {
requestSignature: {
includeRoutes: ["/api/webhooks/*", "/api/admin/*"],
excludeRoutes: ["/api/public/*"],
},
},
},
});
// Or disable for specific routes
server.get("/api/public/status", (req, res) => {
// This route doesn't require signature
res.json({ status: "ok" });
});
Security Best Practices
1. Use Strong Secrets
// ✅ Good: Generate cryptographically secure secrets
const crypto = require("crypto");
const secret = crypto.randomBytes(32).toString("hex");
// ❌ Bad: Weak or predictable secrets
const secret = "password123";
const secret = "my-api-key";
2. Implement Timestamp Validation
// Server-side timestamp validation is automatic
// But you can customize the maximum age
requestSignature: {
maxAge: 300000, // 5 minutes
clockSkew: 30000 // 30 seconds tolerance
}
3. Use HTTPS in Production
// Always use HTTPS to prevent signature interception
const server = createServer({
server: {
https: {
key: fs.readFileSync("server.key"),
cert: fs.readFileSync("server.crt"),
},
},
security: {
requestSignature: { secret: "your-secret" },
},
});
4. Rotate Secrets Regularly
// Implement secret rotation
const secrets = {
current: "current-secret",
previous: "previous-secret", // Allow old signatures during transition
};
// Validate against multiple secrets
function validateSignature(signature, data) {
return secrets.current === signature || secrets.previous === signature; // Grace period
}
Error Handling
The middleware provides clear error messages:
server.get('/api/protected', (req, res) => {
res.json({ message: 'Success!' });
});
// Error responses:
{
"error": "Missing signature header",
"message": "XP-Request-Sig header is required"
}
{
"error": "Invalid signature",
"message": "Request signature verification failed"
}
{
"error": "Signature expired",
"message": "Request signature has expired"
}
Integration Examples
Webhook Endpoint Protection
const server = createServer({
security: {
requestSignature: {
secret: process.env.WEBHOOK_SECRET,
},
},
});
server.post("/webhooks/stripe", (req, res) => {
// Only Stripe can call this endpoint
const event = req.body;
// Process webhook...
res.json({ received: true });
});
API-to-API Communication
// Service A (Client)
const client = {
async callServiceB(endpoint, data) {
const { signature, timestamp } = signRequest(
endpoint,
"POST",
JSON.stringify(data),
SHARED_SECRET
);
return fetch(`http://service-b${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"XP-Request-Sig": signature,
"X-Timestamp": timestamp,
},
body: JSON.stringify(data),
});
},
};
// Service B (Server)
const server = createServer({
security: {
requestSignature: {
secret: SHARED_SECRET,
},
},
});
server.post("/api/data", (req, res) => {
// Trust that this request came from Service A
res.json({ processed: req.body });
});
Mobile App API Protection
// Mobile apps can include the signature in requests
const mobileClient = {
async apiCall(endpoint, data) {
const { signature, timestamp } = signRequest(
endpoint,
"POST",
JSON.stringify(data),
APP_SECRET
);
return fetch(`${API_BASE}${endpoint}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"XP-Request-Sig": signature,
"X-Timestamp": timestamp,
Authorization: `Bearer ${userToken}`, // Additional auth if needed
},
body: JSON.stringify(data),
});
},
};
Troubleshooting
Common Issues
-
"Missing signature header"
- Ensure client includes
XP-Request-Sigheader - Check header name if using custom configuration
- Ensure client includes
-
"Invalid signature"
- Verify secret key matches between client and server
- Check that request data is signed in the correct order:
METHOD + URL + BODY + TIMESTAMP
-
"Signature expired"
- Check system clock synchronization
- Increase
maxAgeif needed - Adjust
clockSkewfor clock differences
Debug Mode
Enable debug logging to troubleshoot signature validation:
const server = createServer({
security: {
requestSignature: {
secret: "your-secret",
debug: true, // Enable detailed logging
},
},
});
This will log signature validation steps, making it easier to identify issues.
Performance Considerations
- Signature validation is computationally lightweight (HMAC-SHA256)
- No external service calls required
- Minimal impact on response times
- Scales well with high request volumes
Migration Guide
From API Keys
// Old approach
server.use("/api/*", (req, res, next) => {
const apiKey = req.headers["x-api-key"];
if (apiKey !== "expected-key") {
return res.status(401).json({ error: "Invalid API key" });
}
next();
});
// New approach
const server = createServer({
security: {
requestSignature: {
secret: "your-secret",
},
},
});
From JWT
// Request signatures can complement JWT
const server = createServer({
security: {
requestSignature: {
secret: "signature-secret",
},
// JWT validation can be added separately
},
});
Request Signature Authentication provides a robust, secure method for API authentication that prevents tampering and ensures request integrity. It's particularly well-suited for server-to-server communication, webhooks, and scenarios where you need cryptographic assurance of request authenticity.