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('./safesky-hmac-auth');

// Your SafeSky API key - REPLACE WITH YOUR ACTUAL API KEY
const API_KEY = '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(
    '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('./safesky-hmac-auth');
const https = require('https');

const API_KEY = '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('./safesky-hmac-auth');
const https = require('https');

const API_KEY = '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('./safesky-hmac-auth');
const https = require('https');

const API_KEY = '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('./safesky-hmac-auth');
const axios = require('axios');

const API_KEY = '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('./safesky-hmac-auth');

const API_KEY = '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