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.

Quick Start

const safeskyAuth = require('./index');

// Your SafeSky API key
const API_KEY = 'ssk_live_YourApiKeyHere';

// Generate authentication headers for a GET request
const headers = safeskyAuth.generateAuthHeaders(
    API_KEY,
    'GET',
    'https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908'
);

// 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',
    'POST',
    'https://api.safesky.app/v1/uav',
    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');
const https = require('https');

const API_KEY = 'ssk_live_YourApiKey';
const url = 'https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';

// 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');
const https = require('https');

const API_KEY = 'ssk_live_YourApiKey';
const url = 'https://api.safesky.app/v1/uav';

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');
const https = require('https');

const API_KEY = 'ssk_live_YourApiKey';
const url = 'https://api.safesky.app/v1/advisory';

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');
const axios = require('axios');

const API_KEY = 'ssk_live_YourApiKey';

async function getNearbyAircraft() {
    const url = 'https://api.safesky.app/v1/uav?lat=50.6970&lng=4.3908&rad=20000';
    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');

const API_KEY = 'ssk_live_YourApiKey';

async function publishUAV() {
    const url = 'https://api.safesky.app/v1/uav';
    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