Last active
March 10, 2025 11:24
-
-
Save The-Don-Himself/e24f3608f11bc71d041efbc37670978e to your computer and use it in GitHub Desktop.
While working on the Griffin Bank API for my fintech, I came across the requirement for Signed Requests, this is my Node implementation that works, no dependencies required.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import env from './config/environment' | |
import crypto from 'crypto'; | |
const { private_key, keyid, api_key } = env.GRIFFIN | |
public getMessageSignaturesHeaders = async ( | |
request_method: 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH', | |
request_path: string, | |
request_body?: object, | |
request_query?: string, | |
) => { | |
// Step 1: Compute content-digest | |
const requestBody = request_body ? JSON.stringify(request_body) : ''; | |
const hash = crypto.createHash('sha512').update(requestBody).digest('base64'); | |
const contentDigest = `sha-512=:${hash}:`; | |
// Step 2: Construct signature-input | |
const created = Math.floor(Date.now() / 1000); | |
const nonce = crypto.randomUUID(); | |
const method = request_method.toUpperCase(); | |
const authority = 'api.griffin.com'; | |
const date = new Date().toUTCString(); | |
const path = request_path; | |
const query = request_query || '?'; | |
// Include @query in the signature parameters if query parameters are present | |
const signatureParams = `("@method" "@authority" "@path" "content-type" "content-length" "date" "content-digest" "@query");created=${created};keyid="${keyid}";nonce="${nonce}"` | |
const signatureInput = `sig1=${signatureParams}`; | |
// Step 3: Construct signing string | |
const signingStringLines = [ | |
`"@method": ${method}`, | |
`"@authority": ${authority}`, | |
`"@path": ${path}`, | |
`"content-type": application/json`, | |
`"content-length": ${Buffer.byteLength(requestBody)}`, | |
`"date": ${date}`, | |
`"content-digest": ${contentDigest}`, | |
`"@query": ${query}`, | |
`"@signature-params": ${signatureParams}` | |
]; | |
// Join lines with newline characters | |
const signingString = signingStringLines.join('\n'); | |
// Step 4: Load Ed25519 private key | |
const privateKey = crypto.createPrivateKey({ | |
key: private_key, // Ensure this is securely stored | |
format: 'pem', | |
type: 'pkcs8', | |
}); | |
// Step 5: Sign the string using Ed25519 | |
const signature = crypto.sign(null, Buffer.from(signingString, 'utf-8'), privateKey); | |
// Step 6: Encode signature in Base64 | |
const signatureBase64 = `sig1=:${signature.toString('base64')}:`; | |
// Step 7: Attach headers to the request | |
const headers = { | |
Host: authority, | |
'Content-Type': 'application/json', | |
Accept: 'application/json', | |
Authorization: `GriffinAPIKey ${api_key}`, // Ensure this is securely stored | |
'Content-Digest': contentDigest, | |
'Content-Length': Buffer.byteLength(requestBody).toString(), | |
Signature: signatureBase64, | |
'Signature-Input': signatureInput, | |
Date: date, | |
}; | |
return headers; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment