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)
Header
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
- Javascript (NodeJS)
- GoLang
- Java
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}`;
}
import java.nio.charset.StandardCharsets;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.Base64;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
public class JwtTokenGenerator {
private static final String JWT_PUBLIC_KEY = "YOUR_JWT_PUBLIC_KEY"; // Replace with your actual jwtPublicKey
private static final String JWT_SECRET = "YOUR_JWT_SECRET"; // Replace with your actual jwtSecret
private static final Charset CHARSET = StandardCharsets.US_ASCII;
private static final String TOKEN_HEADER_PLAIN = "{\"alg\":\"HS256\",\"typ\":\"JWT\"}";
private static String base64UrlEncode(String text) {
return Base64.getUrlEncoder().encodeToString(text.getBytes(CHARSET)).replaceAll("=+$", "");
}
private static String calculateHmacSHA256(String key, String data) throws NoSuchAlgorithmException, InvalidKeyException {
Mac hmacSha256 = Mac.getInstance("HmacSHA256");
SecretKeySpec secretKey = new SecretKeySpec(key.getBytes(CHARSET), "HmacSHA256");
hmacSha256.init(secretKey);
byte[] messageHash = hmacSha256.doFinal(data.getBytes(CHARSET));
// Base64 encode combination
String signature = Base64.getEncoder().encodeToString(messageHash);
// Replace URL possible characters with stand-ins
return signature.replace("+", "-").replace("/", "_").replaceAll("=+$", "");
}
public static String generateJwtToken(String jwtPublicKey, String jwtSecret) throws NoSuchAlgorithmException, InvalidKeyException {
long timestamp = System.currentTimeMillis() / 1000;
long oneHourFromNow = timestamp + 3600;
String payload = "{\"sub\":\"charge-api\",\"iat\":" + timestamp + ",\"exp\":" + oneHourFromNow + ",\"iss\":\"" + jwtPublicKey + "\"}";
String token = base64UrlEncode(TOKEN_HEADER_PLAIN) + "." + base64UrlEncode(payload);
String signature = calculateHmacSHA256(jwtSecret, token);
return "Bearer " + token + "." + signature;
}
public static void main(String[] args) {
try {
String jwtToken = generateJwtToken(JWT_PUBLIC_KEY, JWT_SECRET);
System.out.println(jwtToken);
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
System.err.println("Token generation failed: " + e.getMessage());
}
}
}
package main
import (
"crypto/hmac"
"crypto/sha256"
"encoding/base64"
"encoding/json"
"fmt"
"strings"
"time"
)
const (
jwtPublicKey = "YOUR_JWT_PUBLIC_KEY" // Replace with your actual jwtPublicKey
jwtSecret = "YOUR_JWT_SECRET" // Replace with your actual jwtSecret
)
func base64UrlEncode(src []byte) string {
encoded := base64.StdEncoding.EncodeToString(src)
encoded = strings.ReplaceAll(encoded, "+", "-")
encoded = strings.ReplaceAll(encoded, "/", "_")
encoded = strings.TrimRight(encoded, "=")
return encoded
}
func calculateHmacSHA256(secret string, data string) string {
h := hmac.New(sha256.New, []byte(secret))
h.Write([]byte(data))
return base64UrlEncode(h.Sum(nil))
}
type Payload struct {
Sub string `json:"sub"`
Iat int64 `json:"iat"`
Exp int64 `json:"exp"`
Iss string `json:"iss"`
}
func generateJwtToken(jwtPublicKey string, jwtSecret string) string {
tokenHeader := `{"alg":"HS256","typ":"JWT"}`
now := time.Now()
oneHourFromNow := now.Add(time.Hour).Unix()
payload := Payload{
Sub: "charge-api",
Iat: now.Unix(),
Exp: oneHourFromNow,
Iss: jwtPublicKey,
}
payloadBytes, _ := json.Marshal(payload)
token := base64UrlEncode([]byte(tokenHeader)) + "." + base64UrlEncode(payloadBytes)
signature := calculateHmacSHA256(jwtSecret, token)
return "Bearer " + token + "." + signature
}
func main() {
jwtToken := generateJwtToken(jwtPublicKey, jwtSecret)
fmt.Println(jwtToken)
}