Signature Validation and Authentication
Overview
This document outlines the purpose, functionality, and implementation of a signature validation service written in Node.js using TypeScript. The service ensures secure communication by verifying the authenticity and integrity of incoming requests using a cryptographic signature scheme.
Features
The service performs the following key tasks:
Canonicalization - Orders and structures the HTTP request’s method, URI, query parameters, headers, and body payload in a consistent manner to create a canonical string.
Hashing - Hashes the ordered request body (payload) using SHA-256.
Signature Generation - Generates a signature by combining the canonical string, a secret key, and a timestamp using the HMAC-SHA256 algorithm.
Signature Validation - Compares the provided signature (from the incoming request) with the generated signature using a timing-safe comparison to mitigate timing attacks.
Error Handling - Throws an error if the signatures do not match or if other issues (e.g., timestamp expiration) are detected.
Key Functions and Methods
1. orderJsonByKeysAscending(jsonObj: Record<string, any>): Record<string, any>
orderJsonByKeysAscending(jsonObj: Record<string, any>): Record<string, any>
Purpose: Recursively orders all keys of a JSON object and sorts elements in arrays in ascending order.
Objects: Both the top-level keys and the keys of any nested objects are sorted alphabetically.
Arrays:
If the array contains primitive values (e.g., strings, numbers), the elements are sorted in ascending order.
If the array contains objects, the elements are sorted based on the lexicographical order of their JSON string representations, after their keys have been sorted.
Arrays containing mixed types (both primitives and objects) or complex structures are returned as is without sorting their elements.
Input: Any JavaScript value, including:
Primitive types: string, number, boolean, null, undefined .
Objects: Objects with key-value pairs, including nested objects.
Arrays: Arrays containing any type of elements (primitives, objects, or mixed).
Output: Returns a new JSON object or value with the following modifications:
Objects: All keys are sorted alphabetically at every level of nesting.
Arrays of primitives: Elements are sorted in ascending order.
Arrays of objects: Elements are sorted based on the lexicographical order of their JSON string representations.
Mixed-type arrays: Arrays containing mixed types are returned as is without sorting their elements.
If the input is invalid (e.g., null, undefined, or not an object/array), the function returns the input as is without modification.
Example Usage:
1 const input = {
2 b: 2,
3 a: 1,
4 array: ["banana", "apple", "cherry"],
5 nestedArray: [{ z: 3, y: 2 }, { x: 1 }],
6 mixedArray: [1, { b: 2 }, "string"],
7 object: {
8 d: 4,
9 c: {
10 f: 6,
11 e: 5
12 }
13 }
14 };
15 const orderedPayload = orderJsonByKeysAscending(input);
16 console.log(JSON.stringify(orderedPayload, null, 2));
Example Output:
1 {
2 "a": 1,
3 "array": [
4 "apple",
5 "banana",
6 "cherry"
7 ],
8 "b": 2,
9 "mixedArray": [
10 1,
11 {
12 "b": 2
13 },
14 "string"
15 ],
16 "nestedArray": [
17 {
18 "x": 1
19 },
20 {
21 "y": 2,
22 "z": 3
23 }
24 ],
25 "object": {
26 "c": {
27 "e": 5,
28 "f": 6
29 },
30 "d": 4
31 }
32 }
createCanonicalQueryString(queryParams: { [key: string]: string }): string
Purpose: Sorts query parameters by key and concatenates them into a canonical string.
Input: An object containing query parameters.
Output: A query string with sorted keys.
Example Usage:
1 const queryString = createCanonicalQueryString({ c: '3', a: '1', b: '2' });
2 console.log(queryString); // a=1&b=2&c=3
createCanonicalHeadersString(headers: { [key: string]: string }): string
Purpose: Sorts specific headers (like x-authorization-api-key and x-authorization-timestamp) alphabetically and formats them.
Input: An object containing headers.
Output: A string with sorted headers.
Example Usage:
1 const headers = createCanonicalHeadersString({ 'x-authorization-api-key': 'key', 'x-authorization-timestamp': '123456' });
2 console.log(headers); // x-authorization-api-key:key\nx-authorization-timestamp:123456
hashPayload(payload: Record<string, any>): string
Purpose: Serializes and hashes a sorted payload using SHA-256.
Input: A JSON object (payload).
Output: A SHA-256 hash string
Example Usage:
1 const hashedPayload = hashPayload({ b: 2, a: 1 }); //inside hashPayload we call orderJsonByKeysAscending to obtain the ordered json payload before hashing it.
2 console.log(hashedPayload); // A SHA-256 hash of the sorted payload.
createCanonicalString(method: string, uri: string, queryParams: { [key: string]: string }, headers: { [key: string]: string }, body: Record<string, any>): string
Purpose: Creates a canonical string that combines the http method, URI, query parameters, headers, and hashed payload.
Input: HTTP method, URI, query parameters, headers, and body payload.
Output: A canonical string for signing with format
${method}\n${uri}\n${canonicalQueryString}\n${canonicalHeaders}\n${hashedPayload}
Example Usage:
1 const canonicalString = createCanonicalString('POST', '/api/resource', queryParams, headers, payload);
2 console.log(canonicalString); // A canonical string used for signature generation.
generateCanonicalSignature(secretKey: string, stringToSign: string, timestamp: number): string
Purpose: Generates a HMAC-SHA256 signature using a secret key, the string to sign, and a timestamp.
Input: Secret key, canonical string, and timestamp.
Output: A cryptographic signature string
Example Usage:
1 const signature = generateCanonicalSignature('mySecretKey', 'stringToSign', 1733747167010);
2 ${stringToSign}\n${timestamp} //Final string to be signed with secret key
3 console.log(signature); // A generated HMAC-SHA256 signature.
Conclusion
This service provides a robust solution for validating incoming HTTP requests based on cryptographic signatures. It ensures the integrity and authenticity of the request by comparing a provided signature with a generated one, preventing tampering and unauthorized access.
Was this helpful?