Authentication test
Overview
This documentation details the process of signing and encrypting headers for secure authentication in requests to our API. The process ensures that requests are reliable and secure, preventing unauthorized access and ensuring data integrity.
Atenção!
The GET and DELETE methods hash md5 must be generated with an empty payload
- Python
- PHP
- Node.js
- Java
- C#
#Here, we import the necessary libraries throughout the authentication process.
from jose import jwt
import json
from datetime import datetime
from hashlib import md5
import requests
def get_auth_header(endpoint, method, CLIENT_PRIVATE_KEY, API_KEY, request_body=None):
if request_body is None:
request_body = {}
#The date and time object provided must be in UTC and must follow the ISO 8601 international standard ("2023-06-26T19:48:32.759844Z").
timestamp = datetime.utcnow().strftime("%Y-%m-%dT%H:%M:%S.%fZ")
#We define the JWT encoding algorithm
jwt_header = {
"typ": "JWT",
"alg": "ES512"
}
#Build MD5 hash for header signature using the payload
json_body = json.dumps(request_body)
md5_hash = md5(json_body.encode()).hexdigest()
#Define the JWT body
jwt_body = {
"payload_md5": md5_hash,
"timestamp": timestamp,
"method": method,
"uri": endpoint
}
#Build signed header
encoded_header_token = jwt.encode(
claims=jwt_body,
key=CLIENT_PRIVATE_KEY,
algorithm="ES512",
headers=jwt_header
)
#Build signed header
signed_header = {
"AUTHORIZATION": encoded_header_token,
"API-CLIENT-KEY": API_KEY
}
return signed_header
if __name__ == "__main__":
#we will use the following variables base_url, endpoint, method and request_body. In this example, we will be conducting a POST in /test.
#The following keys in this example are fake. Please use your own keys.
CLIENT_PRIVATE_KEY = "Your Private Key Here"
API_KEY = "Your API Key Here"
BASE_URL = "https://api-auth.sandbox.qitech.app"
METHOD = "POST" #GET ou POST
REQUEST_BODY = {
"name": "QI Tech"
}
#In order to conduct a GET requisition in /test, it is necessary to insert the API Key into the endpoint, while for a post requisition, it is not.
if METHOD == 'GET':
ENDPOINT = f"/test/{API_KEY}"
signed_header = get_auth_header(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY)
response = requests.get(f"{BASE_URL}{ENDPOINT}", headers=signed_header)
else:
ENDPOINT = f"/test/"
signed_header = get_auth_header(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY, REQUEST_BODY)
response = requests.post(f"{BASE_URL}{ENDPOINT}", json=REQUEST_BODY, headers=signed_header)
print(response.status_code)
print(response.json())
<?php
require __DIR__ . '/vendor/autoload.php';
//Here, we import the necessary libraries throughout the authentication process
use Jose\Component\Core\AlgorithmManager;
use Jose\Component\Signature\JWSTokenSupport;
use Jose\Component\Signature\Algorithm\ES512;
use Jose\Component\Signature\Serializer\CompactSerializer;
use Jose\Component\KeyManagement\JWKFactory;
use Jose\Component\Signature\JWSBuilder;
function get_auth_header($endpoint, $method, $privateKeyString, $api_key, $request_body = null) {
if ($request_body === null) {
$request_body = (object)[];
}
//The date and time object provided must be in UTC and must follow the ISO 8601 international standard ("2023-06-26T19:48:32.759844Z").
$microtime_float = microtime(true);
$datetime = new DateTimeImmutable('@' . floor($microtime_float), new DateTimeZone('UTC'));
$timestamp = $datetime->format('Y-m-d\TH:i:s.') . sprintf('%06d', ($microtime_float - floor($microtime_float)) * 1000000) . 'Z';
//We define the JWT encoding algorithm
$header = [
"typ" => "JWT",
"alg" => "ES512"
];
//Build MD5 hash for header signature using the payload
$request_body_json = json_encode($request_body);
$md5_hash = md5($request_body_json);
//These are the necessary pieces of information to sign the header
$payload = [
"payload_md5" => $md5_hash,
"timestamp" => $timestamp,
"method" => $method,
"uri" => $endpoint
];
// Initialize Algorithm Manager with ES512
$algorithmManager = new AlgorithmManager([
new ES512(),
]);
// Inicializar JWS Builder
$jwsBuilder = new JWSBuilder(
$algorithmManager,
new JWSTokenSupport()
);
$privateKey = JWKFactory::createFromKey($privateKeyString);
//Encrypt the header
$jws = $jwsBuilder
->create()
->withPayload(json_encode($payload))
->addSignature($privateKey, $header)
->build();
$serializer = new CompactSerializer();
$jwt = $serializer->serialize($jws, 0);
//Build signed header
$headers = [
'Authorization' => $jwt,
'API-CLIENT-KEY' => $api_key,
];
return $headers;
}
if (php_sapi_name() == 'cli' || (isset($_SERVER['REQUEST_METHOD']) && realpath($_SERVER['SCRIPT_FILENAME']) === __FILE__)) {
//we will use the following variables base_url, endpoint, method and request_body. In this example, we will be conducting a POST in /test.
//The following keys in this example are fake. Please use your own keys.
$base_url = "https://api-auth.sandbox.qitech.app";
$method = "POST"; // HTTP method: "GET" or "POST"
$request_body = ["name" => "QI Tech"];
$api_key = "Your API Key Here";
$privateKeyString = "Your Private Key Here";
$response = null;
//In order to conduct a GET requisition in /test, it is necessary to insert the API Key into the endpoint, while for a post requisition, it is not.
if ($method == 'GET') {
$endpoint = "/test/" . $api_key;
$headers = get_auth_header($endpoint, $method, $privateKeyString, $api_key);
$url = $base_url . $endpoint;
$response = \WpOrg\Requests\Requests::get($url, $headers);
} else {
$endpoint = "/test";
$headers = get_auth_header($endpoint, $method, $privateKeyString, $api_key, $request_body);
$url = $base_url . $endpoint;
$response = \WpOrg\Requests\Requests::post($url, $headers, json_encode($request_body));
}
if ($response) {
echo "HTTP Status Code: " . $response->status_code . "\n";
$json_response = json_decode($response->body, true);
if (json_last_error() === JSON_ERROR_NONE) {
echo "Response JSON:\n";
print_r($json_response);
} else {
echo "Error decoding JSON. Raw Response Text:\n";
echo $response->body . "\n";
}
}
?>
//Here, we import the necessary libraries throughout the authentication process
const jwt = require('jsonwebtoken');
const crypto = require('crypto');
const axios = require('axios');
function getAuthHeader(endpoint, method, client_private_key, api_key, request_body = null) {
if (request_body === null) {
request_body = {};
}
//The date and time object provided must be in UTC and must follow the ISO 8601 international standard ("2023-06-26T19:48:32.759844Z").
const now = new Date();
const isoString = now.toISOString();
const timestamp = isoString.slice(0, -1) + (now.getMilliseconds() * 1000).toString().padStart(6, '0').slice(0, 3) + 'Z';
//We define the JWT encoding algorithm
const jwt_header = {
typ: 'JWT',
alg: 'ES512'
};
//Build MD5 hash for header signature using the payload
const str_body = JSON.stringify(request_body);
const md5_hash = crypto.createHash('md5').update(str_body).digest('hex');
//These are the necessary pieces of information to sign the header
const jwt_body = {
payload_md5: md5_hash,
timestamp: timestamp,
method: method,
uri: endpoint
};
//Encrypt the header
const encoded_header_token = jwt.sign(
jwt_body,
client_private_key,
{
algorithm: 'ES512',
header: jwt_header
}
);
//Build signed header
const signed_header = {
'AUTHORIZATION': encoded_header_token,
'API-CLIENT-KEY': api_key
};
return signed_header;
}
//we will use the following variables base_url, endpoint, method and request_body. In this example, we will be conducting a POST in /test.
//The following keys in this example are fake. Please use your own keys.
async function main() {
const BASE_URL = "https://api-auth.sandbox.qitech.app";
const METHOD = "POST"; //"POST" ou "GET"
const REQUEST_BODY = {
name: "QI Tech"
};
const API_KEY = "Your API Key Here";
const CLIENT_PRIVATE_KEY = "Your Private Key Here";
let ENDPOINT;
let url;
let signed_header;
try {
//In order to conduct a GET requisition in /test, it is necessary to insert the API Key into the endpoint, while for a post requisition, it is not.
if (METHOD === 'GET') {
ENDPOINT = `/test/${API_KEY}`;
url = `${BASE_URL}${ENDPOINT}`;
signed_header = getAuthHeader(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY);
const response = await axios.get(url, { headers: signed_header });
console.log("Status Code:", response.status);
console.log("Response Body:", response.data);
} else {
ENDPOINT = "/test";
url = `${BASE_URL}${ENDPOINT}`; // Corrected string interpolation
signed_header = getAuthHeader(ENDPOINT, METHOD, CLIENT_PRIVATE_KEY, API_KEY, REQUEST_BODY);
const response = await axios.post(url, REQUEST_BODY, { headers: signed_header });
console.log("Status Code:", response.status);
console.log("Response Body:", response.data);
}
} catch (error) {
if (error.response) {
console.error("API Error - Status Code:", error.response.status);
console.error("API Error - Response Data:", error.response.data);
console.error("API Error - Headers:", error.response.headers);
} else if (error.request) {
console.error("Network Error: No response received from server.");
console.error("Request:", error.request);
} else {
console.error("Error setting up request:", error.message);
}
console.error("Full Error Object:", error);
}
}
main();
// To utilize Java we must create a file called qitech-java-client
// Create a file called pom.xml and paste the end of the code inside
// It will be necessary to create a few folders - create the following path; src > main > java > com > qitech > api e insira seu arquivo java dentro com o nome de QItechApiClient.java
// Add your private key inside the root directory in the same project at your poms level
// To run the code, open your terminal or command prompt, search through your projects root and execute the following comand
// mvn clean install exec:java
//Here, we import the necessary libraries throughout the authentication process
package com.qitech.api;
import com.google.gson.Gson;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyFactory;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PrivateKey;
import java.security.Security;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.PKCS8EncodedKeySpec;
import java.text.SimpleDateFormat;
import java.util.Base64;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.TimeZone;
import java.util.concurrent.TimeUnit;
import org.bouncycastle.jce.ECNamedCurveTable;
import org.bouncycastle.jce.spec.ECParameterSpec;
import org.bouncycastle.jce.spec.ECPrivateKeySpec;
public class QItechApiClient {
public static Map<String, String> getAuthHeader(String endpoint, String method, PrivateKey privateKey, String apiKey, Map<String, Object> requestBody) throws NoSuchAlgorithmException {
//O objeto de data e hora informado deve estar em UTC e deve seguir o padrão da norma internacional ISO 8601 ("2023-06-26T19:48:32.759844Z")
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSSSS'Z'");
sdf.setTimeZone(TimeZone.getTimeZone("UTC"));
String timestamp = sdf.format(new Date());
//Build MD5 hash for header signature using the payload
String jsonBody = jsonToString(requestBody);
String md5Hash = md5Hash(jsonBody);
//These are the necessary pieces of information to sign the header
Map<String, Object> jwtBody = new HashMap<>();
jwtBody.put("payload_md5", md5Hash);
jwtBody.put("timestamp", timestamp);
jwtBody.put("method", method);
jwtBody.put("uri", endpoint);
//Encrypt the header
JwtBuilder jwtBuilder = Jwts.builder()
.setClaims(jwtBody)
.signWith(privateKey, SignatureAlgorithm.ES512);
String encodedHeaderToken = jwtBuilder.compact();
//Build signed header
Map<String, String> signedHeader = new HashMap<>();
signedHeader.put("AUTHORIZATION", encodedHeaderToken);
signedHeader.put("API-CLIENT-KEY", apiKey);
return signedHeader;
}
public static void main(String[] args) {
Security.addProvider(new BouncyCastleProvider());
OkHttpClient client = null;
try {
//we will use the following variables base_url, endpoint, method and request_body. In this example, we will be conducting a POST in /test.
//The following keys in this example are fake. Please use your own keys.
final String BASE_URL = "https://api-auth.sandbox.qitech.app";
final String PRIVATE_KEY_FILENAME = "private.key";
final String API_CLIENT_KEY = "Your API Key Here";
final String METHOD = "GET"; //GET ou POST
final Map<String, Object> REQUEST_BODY = new HashMap<>();
REQUEST_BODY.put("name", "QI Tech");
String keyFromFile = readKeyFromFile(PRIVATE_KEY_FILENAME);
PrivateKey privateKey = getPrivateKey(keyFromFile);
String endpoint;
Request request;
//In order to conduct a GET requisition in /test, it is necessary to insert the API Key into the endpoint, while for a post requisition, it is not.
if ("GET".equalsIgnoreCase(METHOD)) {
endpoint = "/test/" + API_CLIENT_KEY;
Map<String, String> headers = getAuthHeader(endpoint, "GET", privateKey, API_CLIENT_KEY, Collections.emptyMap());
request = new Request.Builder()
.url(BASE_URL + endpoint)
.headers(okhttp3.Headers.of(headers))
.get()
.build();
} else {
endpoint = "/test";
Map<String, String> headers = getAuthHeader(endpoint, "POST", privateKey, API_CLIENT_KEY, REQUEST_BODY);
RequestBody body = RequestBody.create(
jsonToString(REQUEST_BODY),
MediaType.parse("application/json; charset=utf-8")
);
request = new Request.Builder()
.url(BASE_URL + endpoint)
.headers(okhttp3.Headers.of(headers))
.post(body)
.build();
}
client = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.build();
System.out.println("--- Sending " + METHOD + " Request ---");
System.out.println("URL: " + BASE_URL + endpoint);
try (Response response = client.newCall(request).execute()) {
System.out.println("\n--- Received Response ---");
System.out.println("Status Code: " + response.code());
if (response.body() != null) {
System.out.println("Response Body: " + response.body().string());
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (client != null) {
client.dispatcher().executorService().shutdown();
client.connectionPool().evictAll();
}
}
}
// --- Helper Methods ---
private static String readKeyFromFile(String filename) throws IOException {
String key = new String(Files.readAllBytes(Paths.get(filename)));
return key.replace("-----BEGIN EC PRIVATE KEY-----", "")
.replace("-----END EC PRIVATE KEY-----", "")
.replace("-----BEGIN PRIVATE KEY-----", "")
.replace("-----END PRIVATE KEY-----", "")
.replaceAll("\\s", "");
}
private static String jsonToString(Map<String, Object> jsonMap) { return new Gson().toJson(jsonMap); }
private static String md5Hash(String text) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("MD5");
byte[] array = md.digest(text.getBytes());
StringBuilder sb = new StringBuilder();
for (byte b : array) { sb.append(String.format("%02x", b)); }
return sb.toString();
}
private static PrivateKey getPrivateKey(final String encodedPvKey) throws IOException {
try {
byte[] derBytes = Base64.getDecoder().decode(encodedPvKey);
KeyFactory keyFactory = KeyFactory.getInstance("EC", BouncyCastleProvider.PROVIDER_NAME);
try {
return keyFactory.generatePrivate(new PKCS8EncodedKeySpec(derBytes));
} catch (InvalidKeySpecException e) {
org.bouncycastle.asn1.sec.ECPrivateKey sec1Key = org.bouncycastle.asn1.sec.ECPrivateKey.getInstance(derBytes);
ECParameterSpec ecParameterSpec = ECNamedCurveTable.getParameterSpec("secp521r1");
ECPrivateKeySpec privateKeySpec = new ECPrivateKeySpec(sec1Key.getKey(), ecParameterSpec);
return keyFactory.generatePrivate(privateKeySpec);
}
} catch (Exception e) {
throw new IOException("Failed to parse private key. Key is corrupted or not a valid EC key.", e);
}
}
}
////POM File
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.qitech.api</groupId>
<artifactId>qitech-api-client</artifactId>
<version>1.0.0</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<!-- HTTP Client -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.12.0</version>
</dependency>
<!-- JSON Web Token (JWT) Handling -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.12.5</version>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.12.5</version>
<scope>runtime</scope>
</dependency>
<!-- Cryptography Provider for ES512 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.78</version>
</dependency>
<!-- JSON Serialization -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.13.0</version>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>3.2.0</version>
<configuration>
<mainClass>com.qitech.api.QItechApiClient</mainClass>
</configuration>
</plugin>
</plugins>
</build>
</project>
//Here, we import the necessary libraries throughout the authentication process.
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Jose;
using Newtonsoft.Json;
public static class QiTechAuthGenerator {
public static string GetAuthorizationHeader(
string endpoint,
string method,
string clientPrivateKey,
object requestBody)
{
string privateKeyBase64 = clientPrivateKey
.Replace("-----BEGIN EC PRIVATE KEY-----", "")
.Replace("-----END EC PRIVATE KEY-----", "")
.Replace("\n", "")
.Replace("\r", "");
using var privateKey = ECDsa.Create();
privateKey.ImportECPrivateKey(Convert.FromBase64String(privateKeyBase64), out _);
string payloadToHash;
if (method.ToUpper() == "GET") {
payloadToHash = "{}";
}
else {
payloadToHash = JsonConvert.SerializeObject(requestBody);
}
//Calculate to md5hash
var payloadMd5Hash = CalculateMd5Hash(payloadToHash);
//These are the necessary pieces of information to sign the header
var jwtBody = new Dictionary<string, object> {
{ "payload_md5", payloadMd5Hash },
{ "timestamp", timestamp },
{ "method", method },
{ "uri", endpoint }
};
//We define the JWT encoding algorithm
var jwtHeader = new Dictionary<string, object> {
{ "typ", "JWT" },
{ "alg", "ES512" }
};
return JWT.Encode(jwtBody, privateKey, JwsAlgorithm.ES512, jwtHeader);
}
//Build MD5 hash for header signature using the payload
private static string CalculateMd5Hash(string input) {
using (var md5 = MD5.Create()) {
byte[] inputBytes = Encoding.UTF8.GetBytes(input);
byte[] hashBytes = md5.ComputeHash(inputBytes);
var builder = new StringBuilder();
foreach (var b in hashBytes) {
builder.Append(b.ToString("x2"));
}
return builder.ToString();
}
}
}
public class QiTechApiClient {
private readonly string _baseUrl;
private readonly string _apiKey;
private readonly string _clientPrivateKey;
public QiTechApiClient(string baseUrl, string apiKey, string clientPrivateKey) {
_baseUrl = baseUrl;
_apiKey = apiKey;
_clientPrivateKey = clientPrivateKey;
}
//Encrypt the header
public async Task<string> CallEndpointAsync(string endpoint, string method, object requestBody) {
var signedHeader = QiTechAuthGenerator.GetAuthorizationHeader(
endpoint,
method,
_clientPrivateKey,
requestBody
);
var url = $"{_baseUrl}{endpoint}";
//Build signed header
using (var client = new HttpClient()) {
client.DefaultRequestHeaders.Add("AUTHORIZATION", signedHeader);
client.DefaultRequestHeaders.Add("API-CLIENT-KEY", _apiKey);
HttpResponseMessage httpResponse;
if (method.ToUpper() == "GET") {
httpResponse = await client.GetAsync(url);
}
else {
var jsonBody = JsonConvert.SerializeObject(requestBody);
var content = new StringContent(jsonBody, Encoding.UTF8, "application/json");
httpResponse = await client.PostAsync(url, content);
}
httpResponse.EnsureSuccessStatusCode();
var responseContent = await httpResponse.Content.ReadAsStringAsync();
return responseContent;
}
}
}
public class Program {
public static async Task Main() {
string response = "";
//we will use the following variables base_url, endpoint, method and request_body. In this example, we will be conducting a POST in /test.
//The following keys in this example are fake. Please use your own keys.
var baseUrl = "https://api-auth.sandbox.qitech.app";
var method = "POST";
var requestBody = new { name = "QI Tech" };
var apiKey = "Your API Key Here";
var clientPrivateKey = @"Your Private Key Here";
try {
var apiClient = new QiTechApiClient(baseUrl, apiKey, clientPrivateKey);
//In order to conduct a GET requisition in /tst, it is necessary to insert the API Key into the endpoint, while for a post requisition, it is not.
if (method.ToUpper() == "GET") {
var endpoint = "/test/" + apiKey;
response = await apiClient.CallEndpointAsync(endpoint, method, null);
}
else {
var endpoint = "/test";
response = await apiClient.CallEndpointAsync(endpoint, method, requestBody);
}
Console.WriteLine("\nAPI Response:");
Console.WriteLine(response);
}
catch (HttpRequestException ex) {
Console.WriteLine($"\nHTTP Error: {ex.Message}");
}
catch (Exception ex) {
Console.WriteLine($"\nAn unexpected error occurred: {ex.Message}");
}
}
}