Backend trust & verification
Ensure secure communication between the Frontegg MCP Gateway and your backend API by verifying request signatures.
Overview
When the MCP Gateway routes requests to your backend API, it includes a cryptographic signature in the request headers. This signature allows your backend to verify that requests are genuinely coming from Frontegg and haven't been tampered with in transit.
How it works
The MCP Gateway signs each outgoing request using the Key secret you configure in the [Portal] → [Settings] → [Basic Configuration]. This signature is added to the request header as frontegg-mcp-signature.
Signature Components
The signature is generated using the following request components:
- HTTP Method (GET, POST, PUT, etc.)
- Request URL (full URL including query parameters)
- Request Headers
- Request Body (if present)
Signature algorithm
- Algorithm: SHA-256 HMAC
- Validity Window: 300 seconds (5 minutes)
- Secret: The Key secret configured in your MCP Gateway settings
Configuring your key secret
- Navigate to the Configuration tab in your agent application
- Locate the MCP Gateway section
- Copy or generate your Key secret
- Store this secret securely in your backend application
Important: Keep your Key secret secure and never expose it in client-side code or public repositories.
Verifying signatures on your backend
To verify incoming requests from the MCP Gateway, your backend must:
- Extract the signature from the
frontegg-mcp-signatureheader - Reconstruct the signature using the same components:
- Request method
- Request URL
- Request headers
- Request body (if present)
- Generate a signature using your Key secret and SHA-256 HMAC
- Compare signatures to ensure they match
Validity window
Signatures include a timestamp and are valid for 5 minutes from creation. This prevents replay attacks where old requests might be reused maliciously.
Your backend should:
- Check the timestamp in the signature
- Reject requests with signatures older than 5 minutes
Code examples
TypeScript
import * as crypto from 'crypto';
interface SignatureComponents {
signature: string;
timestamp: string;
}
function parseSignatureHeader(header: string): SignatureComponents {
const parts = header.split(',');
const signature = parts.find(p => p.startsWith('signature='))?.split('=')[1] || '';
const timestamp = parts.find(p => p.startsWith('t='))?.split('=')[1] || '';
return { signature, timestamp };
}
function verifySignature(
method: string,
url: string,
headers: Record<string, string>,
body: string | null,
signatureHeader: string,
secret: string
): boolean {
// Parse the signature header
const { signature, timestamp } = parseSignatureHeader(signatureHeader);
// Check timestamp validity (within 5 minutes)
const currentTime = Math.floor(Date.now() / 1000);
const requestTime = parseInt(timestamp, 10);
if (currentTime - requestTime > 300) {
console.error('Signature expired');
return false;
}
// Construct the string to sign
const headersString = JSON.stringify(headers);
const bodyString = body || '';
const stringToSign = `${method}:${url}:${headersString}:${bodyString}:${timestamp}`;
// Generate HMAC signature
const hmac = crypto.createHmac('sha256', secret);
hmac.update(stringToSign);
const computedSignature = hmac.digest('hex');
// Compare signatures
return signature === computedSignature;
}
// Example usage in an Express middleware
function fronteggSignatureMiddleware(secret: string) {
return (req: any, res: any, next: any) => {
const signatureHeader = req.headers['frontegg-mcp-signature'];
if (!signatureHeader) {
return res.status(401).json({ error: 'Missing signature header' });
}
const body = req.body ? JSON.stringify(req.body) : null;
const isValid = verifySignature(
req.method,
req.originalUrl,
req.headers,
body,
signatureHeader,
secret
);
if (!isValid) {
return res.status(401).json({ error: 'Invalid signature' });
}
next();
};
}Python
import hmac
import hashlib
import time
import json
from typing import Dict, Optional, Tuple
def parse_signature_header(header: str) -> Tuple[str, str]:
"""Parse the frontegg-mcp-signature header."""
parts = header.split(',')
signature = ''
timestamp = ''
for part in parts:
if part.startswith('signature='):
signature = part.split('=', 1)[1]
elif part.startswith('t='):
timestamp = part.split('=', 1)[1]
return signature, timestamp
def verify_signature(
method: str,
url: str,
headers: Dict[str, str],
body: Optional[str],
signature_header: str,
secret: str
) -> bool:
"""Verify the Frontegg MCP Gateway signature."""
# Parse the signature header
signature, timestamp = parse_signature_header(signature_header)
# Check timestamp validity (within 5 minutes)
current_time = int(time.time())
request_time = int(timestamp)
if current_time - request_time > 300:
print('Signature expired')
return False
# Construct the string to sign
headers_string = json.dumps(headers, separators=(',', ':'))
body_string = body or ''
string_to_sign = f"{method}:{url}:{headers_string}:{body_string}:{timestamp}"
# Generate HMAC signature
computed_signature = hmac.new(
secret.encode('utf-8'),
string_to_sign.encode('utf-8'),
hashlib.sha256
).hexdigest()
# Compare signatures (constant-time comparison)
return hmac.compare_digest(signature, computed_signature)
# Example usage in a Flask application
from flask import Flask, request, jsonify
app = Flask(__name__)
SECRET = 'your-key-secret-from-frontegg'
def require_valid_signature(f):
"""Decorator to verify Frontegg signature."""
def decorated_function(*args, **kwargs):
signature_header = request.headers.get('frontegg-mcp-signature')
if not signature_header:
return jsonify({'error': 'Missing signature header'}), 401
body = request.get_data(as_text=True) if request.data else None
is_valid = verify_signature(
method=request.method,
url=request.url,
headers=dict(request.headers),
body=body,
signature_header=signature_header,
secret=SECRET
)
if not is_valid:
return jsonify({'error': 'Invalid signature'}), 401
return f(*args, **kwargs)
decorated_function.__name__ = f.__name__
return decorated_function
@app.route('/api/endpoint', methods=['POST'])
@require_valid_signature
def protected_endpoint():
return jsonify({'message': 'Request verified successfully'})Note: These examples show the general approach to signature verification. You may need to adjust the string-to-sign construction based on the exact format used by the Frontegg MCP Gateway. Contact Frontegg support for the precise signature format specification.
Security best practices
- Rotate secrets regularly: Update your Key secret periodically and whenever a team member with access leaves
- Use HTTPS: Always use HTTPS for your API endpoints to prevent man-in-the-middle attacks
- Log verification failures: Monitor failed signature verifications as they may indicate security issues
- Reject invalid signatures: Never process requests with invalid or missing signatures
- Secure secret storage: Store your Key secret using secure secret management solutions (e.g., AWS Secrets Manager, HashiCorp Vault)
Troubleshooting
Signature verification fails
If signature verification fails, check:
- Correct secret: Ensure your backend is using the exact Key secret from the Configuration page
- Clock synchronization: Verify your server's clock is synchronized (use NTP)
- Timestamp expiration: Check if the request is being processed within the 5-minute validity window
- Request modifications: Ensure no proxies or middleware are modifying the request before verification
Invalid timestamp errors
If you're receiving timestamp-related errors:
- Check your server's system time
- Ensure your timezone settings are correct
- Consider network latency between the gateway and your backend