Skip to main content
Version: 3.2.0

Authentication

How API Calls Are Authenticated

DMG uses a JSON Web Token (JWT) scheme for Application Layer security. This scheme allows for authentication between the Client and DMG through the use of a pre-shared secret. The DMG authentication scheme has the added benefits of being easily supportable by hardware security modules (HSMs) and ensuring the shared secret is never exposed in a request.

API Authentication

JSON Web Token (JWT) is a compact, URL-safe means of representing claims to be transferred between two parties (RFC 7519). However, DMG and its clients will have access to a pre-shared secret, so the API authentication scheme takes a slightly modified approach to JWT.

Instead of an unchanging bearer token created by the DMG API server, API authentication assumes a unique token created by the Client for each request. The claims are made by the Client, signed with the shared secret, and are unchangeable by any bad actor who may attempt to intercept and tamper with the HTTP request. This authentication scheme also supports hardware security modules (HSMs) and ensures the shared secret is never exposed in a request.

Authentication Token Details

An API authentication token consists of three parts joined with a '.' character:

Token: <the header>.<the payload>.<the signature>

Once created and prefixed with "APIAUTH," the token shall be added to the HTTP request as the "Authorization" header.

Authorization:

The following is a breakdown of the three parts of the API authentication token by example. It assumes that DMG has created and shared an API Public Key (jwtPublicKey) and API Secret (jwtSecret) with the Client as part of client onboarding. (In the following, note that base64 URL encoding differs from standard base64 encoding: '+' becomes '-', '/' becomes '_', and any padding ('=') is removed from the end of the encoded string.)

Note: you can use these example values to validate your DMG token algorithm, but:

  • The JSON fields must be in the same order as the example
  • The JSON must contain NO whitespace (i.e., minimised)

The APIAUTH token header is the base64URL encoded version of a static JSON object:

const header_plain = {"alg": "HS256", "typ": "JWT"}
const header_plain_minimized = {"alg": "HS256","typ": "JWT"}
const header = base64Url_encode(header_plain_no_whitespace)
console.log(header) // eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9

Payload

The APIAUTH token payload is the base64URL encoded version of a JSON object with 4 fields:

{
"sub": "charge-api",
"iat": 1695091096,
"exp": 1695094696,
"iss": "cBBSetDbmdNMEumASbjNDsmLvYXxUR"
}

sub Static value indicating the service for this token. Possible values charge-api

iat Current unique timestamp (blocks replay attacks). Note that each request will require a new timestamp (even for retries).

exp Expiry timestamp for this token. Maximum expiry is 3600 seconds (1 Hour)

iss DMG API Public Key (jwtPublicKey) (ensures identifier ownership)

Signature

The Bearer signature is the base64URL encoded version of the (BINARY) SHA256 HMAC of the header and payload joined by the ‘.’ character. (Note again, the base64URL encoding must be done on the binary output of the SHA256 algorithm, not a hex string version of the output.). For example: Signature_plain = HMACSHA256(header+ "." + payload, api_secret)

For this example, api_secret = “foo”. However, DMG will provide the client with a client-specific secret (as part of client onboarding) before their need to make calls to an actual DMG endpoint.

cosnt signature = crypto.createHmac("sha256", jwtApiSecret).update(data).digest("base64url")
console.log(signature) // bSlKj2szeGmrasULgpn2KfSnSTXMkHpIFTjuNb5bkSc

The final APIAUTH token "the header". "the payload"."the signature" then would look like:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEyMzE3MjMzLCJhcGlfa2V5IjoiaEQyRm9NVDg1Q0JmRmR4RE1hMWRNS0J1S0FPaGVpbU4iLCJhcGlfdXJpIjoiL3BheW1lbnQvbm90aWZ5IiwiYm9keV9oYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9.bSlKj2szeGmrasULgpn2KfSnSTXMkHpIFTjuNb5bkSc

The dots are hard to find, but they are in there!

And the request HTTP header would thus look like this:

Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpYXQiOjEyMzE3MjMzLCJhcGlfa2V5IjoiaEQyRm9NVDg1Q0JmRmR4RE1hMWRNS0J1S0FPaGVpbU4iLCJhcGlfdXJpIjoiL3BheW1lbnQvbm90aWZ5IiwiYm9keV9oYXNoIjoiZTNiMGM0NDI5OGZjMWMxNDlhZmJmNGM4OTk2ZmI5MjQyN2FlNDFlNDY0OWI5MzRjYTQ5NTk5MWI3ODUyYjg1NSJ9.bSlKj2szeGmrasUL gpn2KfSnSTXMkHpIFTjuNb5bkSc

Authentication error response

If an API key is missing, malformed, or invalid, you will receive an HTTP 401 Unauthorized response code.

Examples

const crypto = require("crypto");
const jwtPublicKey = "YOUR_JWT_PUBLIC_KEY"; // Replace with your actual jwtPublicKey
const jwtSecret = "YOUR_JWT_SECRET"; // Replace with your actual jwtSecret

const base64Replacer = function (char) {
switch (char) {
case "+":
return "-";
case "/":
return "_";
case "=":
return "";
default:
return char;
}
};

// Function to Base64 URL encode a string in Node.js
const base64Url_encode = function (text) {
const base64 = Buffer.from(text, "utf-8").toString("base64");
return base64.replace(/[\+/=]/g, base64Replacer);
};

// Function to calculate HMAC-SHA256 using jwtSecret in Node.js
const calculateHmacSHA256 = function (key, data) {
return crypto.createHmac("sha256", key).update(data).digest("base64url");
};

function generateJwtToken(jwtPublicKey, jwtSecret) {
const TOKEN_HEADER_PLAIN = '{"alg":"HS256","typ":"JWT"}';
const timestamp = Math.round(new Date().getTime() / 1000);
const oneHourFromNow = timestamp + 3600;

const payload = JSON.stringify({
sub: "charge-api",
iat: timestamp,
exp: oneHourFromNow,
iss: jwtPublicKey,
});

const token = base64Url_encode(TOKEN_HEADER_PLAIN) + "." + base64Url_encode(payload);
const signature = calculateHmacSHA256(jwtSecret, token);

return `Bearer ${token}.${signature}`;
}