Last updated

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

  1. Navigate to the Configuration tab in your agent application
  2. Locate the MCP Gateway section
  3. Copy or generate your Key secret
  4. 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:

  1. Extract the signature from the frontegg-mcp-signature header
  2. Reconstruct the signature using the same components:
    • Request method
    • Request URL
    • Request headers
    • Request body (if present)
  3. Generate a signature using your Key secret and SHA-256 HMAC
  4. 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:

  1. Correct secret: Ensure your backend is using the exact Key secret from the Configuration page
  2. Clock synchronization: Verify your server's clock is synchronized (use NTP)
  3. Timestamp expiration: Check if the request is being processed within the 5-minute validity window
  4. 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