HMAC Authentication SDK 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 stringbody(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 methodpath(string): Request pathqueryString(string): Query string without leading?host(string): Host header valuetimestamp(string): ISO8601 timestampnonce(string): Unique noncebody(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 stringhmacKey(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:
- GET nearby aircraft - Retrieve traffic in a specific area
- POST UAV position - Submit UAV telemetry data
- POST UAV with query parameters - Submit UAV and get nearby traffic
- POST advisory (GeoJSON) - Publish GeoJSON FeatureCollection with polygon and point
- 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
- Keep your API key secret - Never commit it to version control or expose it in client-side code
- Use environment variables - Store your API key in environment variables:
const API_KEY = process.env.SAFESKY_API_KEY; - Use HTTPS only - The SafeSky API only accepts HTTPS requests
- Handle errors - Always implement proper error handling in production code
How It Works
The HMAC authentication process:
- Derive KID: A public identifier is derived from your API key
- Derive HMAC Key: A signing key is derived using HKDF-SHA256
- Build Canonical Request: A standardized string representation of the request
- Generate Signature: HMAC-SHA256 signature of the canonical request
- 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