Signature Validation

Signature Validation

Overview

For enhanced security, Groove can sign transaction API requests using HMAC-SHA256. Your platform should validate these signatures to ensure requests are authentic and haven’t been tampered with.

Prerequisites

Before validating signatures, you’ll need:

  1. Access Key Value: Provided by Groove during integration setup
  2. Secret: Base64 decoded version of the Access Key Value
Secret = base64_decode(<Access Key Value>)

Note: The Access Key is a pseudo-random HS512 secret provided by Groove.

Authorization Header

Each signed request from Groove includes an Authorization header with the following format:

Authorization: HMAC-SHA256 Signature={Signature}

Header Syntax

Component Description Required
HMAC-SHA256 Authorization scheme Optional (but recommended to verify)
Signature Base64 encoded HMAC-SHA256 hash Required

Signature Calculation

The signature is calculated using:

Signature = base64_encode(HMACSHA256(Path-And-Query, Secret))

Where:

  • Path-And-Query: The canonical representation of the request path and query string
  • Secret: The base64 decoded Access Key Value

Path-And-Query Format

The Path-And-Query is the concatenation of:

  1. The request’s absolute URI path
  2. The query string (including the ?)

Example

For a request to:

https://your-domain.com/groove?request=getbalance&gamesessionid=123&accountid=456

The Path-And-Query would be:

/groove?request=getbalance&gamesessionid=123&accountid=456

Implementation Example

PHP Implementation

function validateGrooveSignature($request, $accessKeyValue) {
    // Get the Authorization header
    $authHeader = $_SERVER['HTTP_AUTHORIZATION'] ?? '';
    
    // Extract signature from header
    if (!preg_match('/HMAC-SHA256\s+Signature=(.+)/', $authHeader, $matches)) {
        return false;
    }
    
    $providedSignature = $matches[1];
    
    // Decode the secret
    $secret = base64_decode($accessKeyValue);
    
    // Build Path-And-Query
    $pathAndQuery = $_SERVER['REQUEST_URI'];
    
    // Calculate expected signature
    $hash = hash_hmac('sha256', $pathAndQuery, $secret, true);
    $expectedSignature = base64_encode($hash);
    
    // Compare signatures (constant-time comparison)
    return hash_equals($expectedSignature, $providedSignature);
}

// Usage
$accessKeyValue = "your_base64_access_key_from_groove";
if (validateGrooveSignature($_REQUEST, $accessKeyValue)) {
    // Process the valid request
    processTransaction();
} else {
    // Reject invalid signature
    http_response_code(401);
    echo json_encode([
        "code" => 401,
        "status" => "Unauthorized",
        "message" => "Invalid signature"
    ]);
}

Node.js Implementation

const crypto = require('crypto');

function validateGrooveSignature(req, accessKeyValue) {
    // Get the Authorization header
    const authHeader = req.headers['authorization'] || '';
    
    // Extract signature from header
    const match = authHeader.match(/HMAC-SHA256\s+Signature=(.+)/);
    if (!match) {
        return false;
    }
    
    const providedSignature = match[1];
    
    // Decode the secret
    const secret = Buffer.from(accessKeyValue, 'base64');
    
    // Build Path-And-Query
    const pathAndQuery = req.originalUrl;
    
    // Calculate expected signature
    const hash = crypto
        .createHmac('sha256', secret)
        .update(pathAndQuery)
        .digest('base64');
    
    // Compare signatures (constant-time comparison)
    return crypto.timingSafeEqual(
        Buffer.from(hash),
        Buffer.from(providedSignature)
    );
}

// Usage in Express.js
app.get('/groove', (req, res) => {
    const accessKeyValue = process.env.GROOVE_ACCESS_KEY;
    
    if (!validateGrooveSignature(req, accessKeyValue)) {
        return res.status(401).json({
            code: 401,
            status: "Unauthorized",
            message: "Invalid signature"
        });
    }
    
    // Process valid request
    processTransaction(req, res);
});

Python Implementation

import hmac
import hashlib
import base64
from flask import request, jsonify

def validate_groove_signature(access_key_value):
    # Get the Authorization header
    auth_header = request.headers.get('Authorization', '')
    
    # Extract signature from header
    import re
    match = re.search(r'HMAC-SHA256\s+Signature=(.+)', auth_header)
    if not match:
        return False
    
    provided_signature = match.group(1)
    
    # Decode the secret
    secret = base64.b64decode(access_key_value)
    
    # Build Path-And-Query
    path_and_query = request.full_path
    if path_and_query.endswith('?'):
        path_and_query = path_and_query[:-1]
    
    # Calculate expected signature
    hash_obj = hmac.new(
        secret,
        path_and_query.encode('utf-8'),
        hashlib.sha256
    )
    expected_signature = base64.b64encode(hash_obj.digest()).decode('utf-8')
    
    # Compare signatures (constant-time comparison)
    return hmac.compare_digest(expected_signature, provided_signature)

# Usage in Flask
@app.route('/groove')
def handle_groove_request():
    access_key_value = os.environ.get('GROOVE_ACCESS_KEY')
    
    if not validate_groove_signature(access_key_value):
        return jsonify({
            'code': 401,
            'status': 'Unauthorized',
            'message': 'Invalid signature'
        }), 401
    
    # Process valid request
    return process_transaction()

Testing Signature Validation

Test Example

Given:

  • Access Key Value: dGVzdF9zZWNyZXRfa2V5XzEyMw==
  • Request URL: /groove?request=getbalance&accountid=123
  • Expected Path-And-Query: /groove?request=getbalance&accountid=123

Steps:

  1. Decode secret: base64_decode("dGVzdF9zZWNyZXRfa2V5XzEyMw==")
  2. Calculate HMAC-SHA256 of Path-And-Query with secret
  3. Base64 encode the hash
  4. Compare with provided signature

Security Best Practices

  1. Always validate signatures in production environments
  2. Use constant-time comparison to prevent timing attacks
  3. Store Access Key securely (environment variables, secure vault)
  4. Log validation failures for security monitoring
  5. Reject requests with invalid or missing signatures
  6. Rotate Access Keys periodically as per security policy
  7. Use HTTPS for all communications

Common Issues and Solutions

Issue 1: Signature Mismatch

Cause: Incorrect Path-And-Query construction Solution: Ensure you’re using the exact request URI including all query parameters

Issue 2: Encoding Problems

Cause: Incorrect base64 decoding of Access Key Solution: Verify the Access Key is properly base64 decoded before use

Issue 3: Missing Authorization Header

Cause: Header not properly forwarded by proxy/load balancer Solution: Configure your infrastructure to forward Authorization headers

Issue 4: Query Parameter Order

Cause: Parameters reordered by framework Solution: Use the raw request URI as received, not reconstructed

Response for Invalid Signatures

When signature validation fails, return:

{
    "code": 401,
    "status": "Unauthorized",
    "message": "Invalid signature",
    "apiversion": "1.2"
}

Integration Checklist

  • Receive Access Key Value from Groove
  • Implement signature validation in your chosen language
  • Test with sample requests from Groove
  • Handle both signed and unsigned requests (during transition)
  • Configure proper error responses
  • Set up monitoring for validation failures
  • Document your validation endpoint

Note on Optional Implementation

While signature validation is highly recommended for production environments, it may be optional during initial integration and testing phases. Coordinate with Groove to determine when signature validation should be enabled for your integration.