Skip to main content

HMAC Auth for Node.js

This SDK provides easy-to-use functions for authenticating requests to the SafeSky API using HMAC signatures.

Click here to download the SDK

Installation

No external dependencies required! Simply copy the SDK file to your project:

# Copy the SDK to your project
cp safesky-hmac-auth.js /path/to/your/project/

Requirements:

    Node.js 16+

    Quick Start

    const safeskyAuth = require('./index'safesky-hmac-auth');
    
    // Your SafeSky API key - REPLACE WITH YOUR ACTUAL API KEY
    const API_KEY = 'ssk_live_YourApiKeyHere'YOUR_SAFESKY_API_KEY_HERE';
    
    // Generate authentication headers for a GET request
    const headers = safeskyAuth.generateAuthHeaders(
        API_KEY,
        'GET',
        'https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908'
        // 'https://public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908'  // Production
    );
    
    // Use the headers in your HTTP request
    console.log(headers);
    // {
    //   'Authorization': 'SS-HMAC Credential=xxx/v1, SignedHeaders=host;x-ss-date;x-ss-nonce, Signature=yyy',
    //   'X-SS-Date': '2025-11-12T14:30:00Z',
    //   'X-SS-Nonce': '550e8400-e29b-41d4-a716-446655440000',
    //   'X-SS-Alg': 'SS-HMAC-SHA256-V1'
    // }
    

    API Reference

    generateAuthHeaders(apiKey, method, url, body = '')

    Generates all required HMAC authentication headers for a SafeSky API request.

    Parameters:

    • apiKey (string): Your SafeSky API key (e.g., ssk_live_...)
    • method (string): HTTP method (GET, POST, etc.)
    • url (string): Full request URL including protocol, host, path, and query string
    • body (string, optional): Request body for POST/PUT requests (default: empty string)

    Returns: Object containing all required headers

    Example:

    const headers = safeskyAuth.generateAuthHeaders(
        'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE',
        'POST',
        'https://sandbox-public-api.safesky.app/v1/uav',
        // 'https://public-api.safesky.app/v1/uav',  // Production
        JSON.stringify({id: 'uav1', lat: 50.69, lng: 4.39})
    );
    
    deriveKid(apiKey)

    Derives the KID (Key Identifier) from an API key.

    Parameters:

    • apiKey (string): Your SafeSky API key

    Returns: String - The derived KID in base64url format

    deriveHmacKey(apiKey)

    Derives the HMAC signing key from an API key using HKDF-SHA256.

    Parameters:

    • apiKey (string): Your SafeSky API key

    Returns: Buffer - The derived HMAC key (32 bytes)

    generateNonce()

    Generates a cryptographically secure nonce (UUID v4 format).

    Returns: String - A UUID v4 nonce

    generateTimestamp()

    Generates the current timestamp in ISO8601 format.

    Returns: String - ISO8601 timestamp (e.g., 2025-11-12T14:30:00Z)

    buildCanonicalRequest(method, path, queryString, host, timestamp, nonce, body)

    Builds the canonical request string for HMAC signature.

    Parameters:

    • method (string): HTTP method
    • path (string): Request path
    • queryString (string): Query string without leading ?
    • host (string): Host header value
    • timestamp (string): ISO8601 timestamp
    • nonce (string): Unique nonce
    • body (string): Request body

    Returns: String - The canonical request string

    generateSignature(canonicalRequest, hmacKey)

    Generates HMAC-SHA256 signature for the canonical request.

    Parameters:

    • canonicalRequest (string): The canonical request string
    • hmacKey (Buffer): The derived HMAC signing key

    Returns: String - The signature in lowercase hexadecimal format

    Complete Examples

    The SDK includes 5 complete examples in example.js:

    1. GET nearby aircraft - Retrieve traffic in a specific area
    2. POST UAV position - Submit UAV telemetry data
    3. POST UAV with query parameters - Submit UAV and get nearby traffic
    4. POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
    5. Manual step-by-step - Detailed authentication flow
    Example 1: GET Request
    const safeskyAuth = require('./index'safesky-hmac-auth');
    const https = require('https');
    
    const API_KEY = 'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE';
    const url = 'https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';
    // const url = 'https://public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';  // Production
    
    // Generate auth headers
    const authHeaders = safeskyAuth.generateAuthHeaders(API_KEY, 'GET', url);
    
    // Make request
    const urlObj = new URL(url);
    const options = {
        hostname: urlObj.hostname,
        path: urlObj.pathname + urlObj.search,
        method: 'GET',
        headers: {
            ...authHeaders,
            'Content-Type': 'application/json'
        }
    };
    
    https.get(options, (res) => {
        let data = '';
        res.on('data', (chunk) => data += chunk);
        res.on('end', () => console.log(JSON.parse(data)));
    });
    
    Example 2: POST Request
    const safeskyAuth = require('./index'safesky-hmac-auth');
    const https = require('https');
    
    const API_KEY = 'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE';
    const url = 'https://sandbox-public-api.safesky.app/v1/uav';
    // const url = 'https://public-api.safesky.app/v1/uav';  // Production
    
    const body = JSON.stringify([{
        id: 'my_uav_001',
        altitude: 110,
        latitude: 50.69378,
        longitude: 4.39201,
        last_update: Math.floor(Date.now() / 1000),
        status: 'AIRBORNE',
        call_sign: 'Test UAV'
    }]);
    
    // Generate auth headers (include body for POST)
    const authHeaders = safeskyAuth.generateAuthHeaders(API_KEY, 'POST', url, body);
    
    // Make request
    const urlObj = new URL(url);
    const options = {
        hostname: urlObj.hostname,
        path: urlObj.pathname,
        method: 'POST',
        headers: {
            ...authHeaders,
            'Content-Type': 'application/json',
            'Content-Length': Buffer.byteLength(body)
        }
    };
    
    const req = https.request(options, (res) => {
        let data = '';
        res.on('data', (chunk) => data += chunk);
        res.on('end', () => console.log(JSON.parse(data)));
    });
    
    req.write(body);
    req.end();
    
    Example 3: POST Advisory (GeoJSON)
    const safeskyAuth = require('./index'safesky-hmac-auth');
    const https = require('https');
    
    const API_KEY = 'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE';
    const url = 'https://sandbox-public-api.safesky.app/v1/advisory';
    // const url = 'https://public-api.safesky.app/v1/advisory';  // Production
    
    const body = JSON.stringify({
        type: "FeatureCollection",
        features: [
            {
                type: "Feature",
                properties: {
                    id: "my_advisory_id1",
                    max_altitude: 111,
                    last_update: Math.floor(Date.now() / 1000),
                    call_sign: "Advisory test with polygon",
                    remarks: "Inspection powerlines"
                },
                geometry: {
                    type: "Polygon",
                    coordinates: [[[4.3948, 50.6831], [4.3952, 50.6832], /* ... more coordinates ... */]]
                }
            },
            {
                type: "Feature",
                properties: {
                    id: "my_advisory_id2",
                    max_altitude: 150,
                    max_distance: 500,
                    last_update: Math.floor(Date.now() / 1000),
                    call_sign: "Advisory test with a point",
                    remarks: "Inspection rails"
                },
                geometry: {
                    type: "Point",
                    coordinates: [4.4, 50.7]
                }
            }
        ]
    });
    
    // Generate auth headers
    const authHeaders = safeskyAuth.generateAuthHeaders(API_KEY, 'POST', url, body);
    
    // Make request (similar to Example 2)
    
    Example 4: Using with Axios
    const safeskyAuth = require('./index'safesky-hmac-auth');
    const axios = require('axios');
    
    const API_KEY = 'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE';
    
    async function getNearbyAircraft() {
        const url = 'https://sandbox-public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';
        // const url = 'https://public-api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';  // Production
        const authHeaders = safeskyAuth.generateAuthHeaders(API_KEY, 'GET', url);
        
        const response = await axios.get(url, {
            headers: {
                ...authHeaders,
                'Content-Type': 'application/json'
            }
        });
        
        return response.data;
    }
    
    getNearbyAircraft()
        .then(data => console.log(data))
        .catch(error => console.error(error));
    
    Example 5: Using with Fetch API
    const safeskyAuth = require('./index'safesky-hmac-auth');
    
    const API_KEY = 'ssk_live_YourApiKey'YOUR_SAFESKY_API_KEY_HERE';
    
    async function publishUAV() {
        const url = 'https://sandbox-public-api.safesky.app/v1/uav';
        // const url = 'https://public-api.safesky.app/v1/uav';  // Production
        const body = JSON.stringify([{
            id: 'my_uav_001',
            altitude: 110,
            latitude: 50.69378,
            longitude: 4.39201,
            last_update: Math.floor(Date.now() / 1000)
        }]);
        
        const authHeaders = safeskyAuth.generateAuthHeaders(API_KEY, 'POST', url, body);
        
        const response = await fetch(url, {
            method: 'POST',
            headers: {
                ...authHeaders,
                'Content-Type': 'application/json'
            },
            body: body
        });
        
        return await response.json();
    }
    
    publishUAV()
        .then(data => console.log(data))
        .catch(error => console.error(error));
    

    Running Examples

    ## Run the provided example file
    node example.js
    

    Security Notes

    1. Keep your API key secret - Never commit it to version control or expose it in client-side code
    2. Use environment variables - Store your API key in environment variables:
      const API_KEY = process.env.SAFESKY_API_KEY;
      
    3. Use HTTPS only - The SafeSky API only accepts HTTPS requests
    4. Handle errors - Always implement proper error handling in production code

    How It Works

    The HMAC authentication process:

    1. Derive KID: A public identifier is derived from your API key
    2. Derive HMAC Key: A signing key is derived using HKDF-SHA256
    3. Build Canonical Request: A standardized string representation of the request
    4. Generate Signature: HMAC-SHA256 signature of the canonical request
    5. Set Headers: Add Authorization, X-SS-Date, X-SS-Nonce, and X-SS-Alg headers

    This ensures:

    • Authentication: Only holders of valid API keys can make requests
    • Integrity: Request data cannot be modified in transit
    • Replay Protection: Each request uses a unique nonce and timestamp

    Troubleshooting

    "Invalid signature" error
    • Ensure your API key is correct
    • Verify the request URL, method, and body exactly match what you're signing
    • Check that the timestamp is within ±5 minutes of server time
    "Replay attack detected" error
    • Don't reuse the same nonce within the validity window
    • Generate a fresh nonce for each request using generateNonce()
    "Clock skew too large" error
    • Ensure your system clock is synchronized
    • The timestamp must be within ±5 minutes of the server time

    Support

    For questions or issues:

    • Email: support@safesky.app
    • Documentation: https://docs.safesky.app

    License

    MIT License - See LICENSE file for details