Full Example
API Signed Request Examples
Last updated
API Signed Request Examples
Last updated
If you use the to make your API calls, the request signature is generated for you.
const buffer = require('buffer');
const crypto = require('crypto');
/* Message Signing Helpers */
/**
* Convert a string to an array buffer
* @param {string} str
* @returns {string}
*/
function str2ab(str) {
const buf = new ArrayBuffer(str.length);
const bufView = new Uint8Array(buf);
for (let i = 0, strLen = str.length; i < strLen; i++) {
bufView[i] = str.charCodeAt(i);
}
return buf;
}
/**
* Convert a string to base64-url safe encoding
* @param {string} str
* @returns {string}
*/
function base64UrlEncode(str) {
return Buffer.from(str).toString('base64')
.replace(/\+/g, '-')
.replace(/\//g, '_')
.replace(/=+$/, '');
}
/**
* Import a private key and return a CryptoKey
* @param {string} privateKeyDer base64 encoded private key der
* @returns {Promise<CryptoKey>}
*/
async function getPrivateKeyFromDer(privKeyDer) {
const decoded = Buffer.from(privKeyDer, 'base64').toString('binary');
const buf = str2ab(decoded);
return crypto.subtle.importKey('pkcs8', buf, {
name: 'ECDSA',
namedCurve: 'P-256',
}, true, ['sign']);
}
/**
* Sign a message with a private CryptoKey
* @param {CryptoKey} privateKey
* @param {string} message The message to sign
* @returns {string} The signed message
*/
async function signMessage(privateKey, message) {
return crypto.subtle.sign(
{
name: 'ECDSA',
hash: 'SHA-256'
},
privateKey,
new TextEncoder().encode(message)
);
}
/**
* Create a request signature
* @param {CryptoKey} privateKey
* @param {string} date HTTP header date value
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
*/
async function createRequestSignature({
privateKey,
date,
method,
path,
queryString = undefined
}) {
const nonce = Date.now().toString();
const sigParts = [
method.toUpperCase(),
path,
queryString,
date,
nonce
].filter(str => str && str.length !== 0);
const signature = await signMessage(privateKey, sigParts.join('\n'));
return base64UrlEncode(signature) + '.' + base64UrlEncode(nonce);
}
/* HTTP Request */
const API_URL = 'https://int.quadrata.com';
const API_KEY = '...';
/**
* Send a signed request
* @param {CryptoKey} privateKey
* @param {string} method HTTP method value
* @param {string} path HTTP path
* @param {string} [queryString] HTTP path query string (? not included)
* @param {object} [body] HTTP body payload
* @param {object} [extraHeaders] Optional headers to send with the request
* @returns {Promise<Response|void>}
*/
async function makeRequest({
privateKey,
method,
path,
queryString = undefined,
body = undefined,
extraHeaders = undefined
}) {
const date = (new Date()).toUTCString();
const signature = await createRequestSignature({
privateKey,
method,
path,
queryString,
date
});
const headers = {
...extraHeaders,
'Authorization': `Basic ${Buffer.from(API_KEY).toString('base64')}`,
'Date': date,
'Signature': signature
};
let urlPath = `${API_URL}${path}`;
if (queryString && queryString.length !== 0) {
urlPath += `?${queryString}`;
}
const response = await fetch(urlPath, {
method,
headers,
body: body ? JSON.stringify(body) : undefined
});
if (!response.ok) {
console.error(response);
return;
}
return response.json();
}
/**
* Base64 Encoded Private Key DER
* @type {string}
*/
const privateKeyDer = '...';
/**
* @type {CryptoKey}
*/
const privateKey = await getPrivateKeyFromDer(privateKeyDer);
/**
* @type {string}
*/
const walletAddress = '...';
// make the request
const json = await makeRequest({
method: 'GET',
privateKey: privateKey,
path: `/api/v1/privacy/grants/${encodeURIComponent(walletAddress)}`,
});
import base64
import requests
from cryptography.hazmat.primitives import serialization
from cryptography.hazmat.primitives.asymmetric.ec import EllipticCurvePrivateKey, ECDSA
from cryptography.hazmat.primitives import hashes
from datetime import datetime, timezone
from secrets import token_hex
from typing import Any, Dict, Optional, Union
API_URL = 'http://int.quadrata.com'
API_KEY = b'...'
PRIVATE_KEY_DER = b'...' # base64 encoded private key der
def b64_url_encode(bstr: bytes) -> str:
return base64.urlsafe_b64encode(bstr).rstrip(b'=').decode('utf-8')
def private_key_from_der(
der: bytes,
password: Optional[bytes] = None
) -> EllipticCurvePrivateKey:
return serialization.load_der_private_key(base64.b64decode(der), password)
def create_sig(
priv_key: EllipticCurvePrivateKey,
method: str,
path: str,
date: str,
query_string: Optional[str] = None
) -> str:
nonce = token_hex(10)
return b64_url_encode(
priv_key.sign(
'\n'.join(
list(
filter(
lambda s: s and len(s) > 0,
[
method.upper(),
path,
query_string if query_string else None,
date,
nonce,
],
)
)
).encode('utf-8'),
ECDSA(hashes.SHA256()),
)
) + (f'.{b64_url_encode(bytes(nonce, "utf-8"))}')
def make_request(
path: str,
method: str = 'get',
query_string: Optional[str] = None,
extra_headers: Optional[Dict[str, Any]] = None
) -> str:
request_action = getattr(requests, method.lower(), None)
if request_action is None:
raise ValueError(f'Invalid method provided: {method}')
date = datetime.now(timezone.utc).strftime('%a, %d %b %Y %H:%M:%S GMT')
sig = create_sig(
priv_key=private_key_from_der(PRIVATE_KEY_DER),
method=method,
path=path,
date=date,
query_string=query_string
)
url_path = f'{API_URL}{path}'
if query_string is not None and len(query_string) != 0:
url_path += f'?{query_string}'
response = request_action(
url_path,
headers={
'Authorization': f'Basic {base64.b64encode(API_KEY).decode()}',
'Signature': sig,
'Date': date
} | (extra_headers if extra_headers is not None else {})
)
return response.json()
wallet_address = '...'
api_response = make_request(f'/api/v1/privacy/grants/{wallet_address}')