DMDS API

Section: Help: Signing and Authenticating REST Requests

Topics

Intro

Authentication is the process of proving your identity to the system. Identity is an important factor in DMDS API access control decisions. Requests are allowed or denied in part based on the identity of the requester. As a developer, you'll be making requests that invoke these privileges so you'll need to prove your identity to the system by authenticating your requests. This section shows you how.

The DMDS REST API uses a custom HTTP scheme based on a keyed-HMAC (Hash Message Authentication Code) for authentication. To authenticate a request, you first concatenate selected elements of the request to form a string. You then use your DMDS Secret Access Key to calculate the HMAC of that string. Informally, we call this process "signing the request," and we call the output of the HMAC algorithm the "signature" because it simulates the security properties of a real signature. Finally, you add this signature as a parameter of the request, using the syntax described in this section.

When the system receives an authenticated request, it fetches the DMDS Secret Access Key that you claim to have, and uses it in the same way to compute a "signature" for the message it received. It then compares the signature it calculated against the signature presented by the requester. If the two signatures match, then the system concludes that the requester must have access to the DMDS Secret Access Key, and therefore acts with the authority of the principal to whom the key was issued. If the two signatures do not match, the request is dropped and the system responds with an error message.

Example Authenticated DMDS REST API Request

GET /api/v1/orders/12345 HTTP/1.1
Host: api.dmds.com

x-dmds-date: 2012-01-01T18:01:31
Authorization: DMDS-API E018F632-5D05-4068-A5E7-11F3161F1AD2:frJIUN8DYpKDtOLCwo//yllqDzg=

Authentication Header

The DMDS REST API uses the standard HTTPAuthorization header to pass authentication information. (The name of the standard header is unfortunate because it carries authentication information, not authorization). Under the DMDS API authentication scheme, the Authorization header has the following form:

Authorization: DMDS-API DMDS_Access_API_Key:Signature

Developers are issued an DMDS Access/API Key ID and DMDS Secret Access Key when they register. For request authentication, DMDS_Access_API_Key element identifies the secret key that was used to compute the signature, and (indirectly) the developer making the request.

The Signature element is the RFC 2104HMAC-SHA1 of selected elements from the request, and so the Signature part of the Authorization header will vary from request to request. If the request signature calculated by the system matches the Signature included with the request, then the requester will have demonstrated possession to the DMDS Secret Access Key. The request will then be processed under the identity, and with the authority, of the developer to whom the key was issued.

Following is pseudo-grammar that illustrates the construction of the Authorization request header ('\n' means the Unicode code point U+000A commonly called newline).

Note NOTE
Pay special attention to the case sensitivity in the pseudo-grammar, it's very important!
Authorization = "DMDS-API" + " " + DMDS_Access_API_KeyID + ":" + Signature


Signature = Base64( HMAC-SHA1( YourDMDSSecretAccessKeyID , UTF-8-Encoding-Of( StringToSign ) ) )

StringToSign = UPPERCASE( HTTP-Verb ) + "\n" +
    UPPERCASE( Date ) + "\n" +
    CanonicalizedResource

CanonicalizedResource = UPPERCASE( [ "/" ] + <HTTP-Request-URI, from the protocol name up to the query string> )

HMAC-SHA1 is an algorithm defined by RFC 2104 (go to RFC 2104 - Keyed-Hashing for Message Authentication ). The algorithm takes as input two byte-strings: a key and a message. For DMDS API Request authentication, use your DMDS Secret Access Key (YourDMDSSecretAccessKeyID) as the key, and the UTF-8 encoding of the StringToSign as the message. The output of HMAC-SHA1 is also a byte string, called the digest. The Signature request parameter is constructed by Base64 encoding this digest.


Time Stamp Requirement

A valid time stamp (using either the HTTP Date header or an x-dmds-date alternative) is mandatory for authenticated requests. Furthermore, the client time-stamp included with an authenticated request must be within 15 minutes of the DMDS API system time when the request is received.
If not, the request will fail with the RequestTimeExpired error status code. The intention of these restrictions is to limit the possibility that intercepted requests could be replayed by an adversary.

Some HTTP client libraries do not expose the ability to set the Date header for a request. If you have trouble including the value of the 'Date' header in the canonicalized headers, you can set the time-stamp for the request using an 'x-dmds-date' header instead. The value of the x-dmds-date header must either be in one of the RFC 2616 formats (http://www.ietf.org/rfc/rfc2616.txt) or UTC format (see examples below). When a x-dmds-date header is present in a request, the system will ignore any Date header when computing the request signature.

Note NOTE
Whichever date format is used, must also be used in the StringToSign.
[ RFC 2616 Formats ]

Sun, 06 Nov 1994 08:49:37 GMT  ; RFC 822, updated by RFC 1123
Sunday, 06-Nov-94 08:49:37 GMT ; RFC 850, obsoleted by RFC 1036
Sun Nov  6 08:49:37 1994       ; ANSI C's asctime() format

[ UTC Format ]

YYYY-MM-DDTHH:MM:SS ; Supported UTC format
2012-01-01T19:34:55 ; Jan 01, 2012, 19:34:55


Authentication Examples

The examples in this section use the (non-working) credentials in the following table:

Parameter Value
DMDS APIKeyID DAE1901D-05B5-499E-AD88-F80BA036E346
DMDSSecretAccessKeyID DBF69104-987E-4E26-A229-D5D9A13FA855

In the example StringToSign(s), formatting is not significant and \n means the Unicode code point U+000A commonly called newline.

Example #1: GET ORDER

This example gets an order with orderID: 123

Note NOTE
In this example the Date header can have mixed case, but when canonicalizing the StringToSign, we MUST convert the date string into UPPERCASE.
Request
GET /api/v1/ad/orders/123
Host: api.dmds.com
Date: Sun, 01 Jan 2012 08:30:00 GMT

Authorization: DMDS-API DAE1901D-05B5-499E-AD88-F80BA036E346:0WD81XrxMJGCAurY4JT+uebpj9o=

            
StringToSign
GET\n
SUN, 01 JAN 2012 08:30:00 GMT\n
/API/V1/AD/ORDERS/123
            

Example #2: GET ORDER REDUX

This example gets an order with orderID: 123, but uses the custom x-dmds-date header

Note NOTE
In this example the x-dmds-date header can have mixed case, but when canonicalizing the StringToSign, we MUST convert the date string into UPPERCASE.
Request
GET /api/v1/ad/orders/123
Host: api.dmds.com
x-dmds-date: Sun, 01 Jan 2012 08:30:00 GMT

Authorization: DMDS-API DAE1901D-05B5-499E-AD88-F80BA036E346:0WD81XrxMJGCAurY4JT+uebpj9o=

            
StringToSign
GET\n
SUN, 01 JAN 2012 08:30:00 GMT\n
/API/V1/AD/ORDERS/123
            

Example #3: GET FILES

This example gets video files

Note NOTE
In this example, we are using UTC date format (YYYY-MM-DDTHH:MM:SS), also pay attention to the fact that the request has a querystring, but the canonicalized resource does not.
Request
GET /api/v1/ad/files/video?dayRange=30&searchFilter=test
Host: api.dmds.com
x-dmds-date: 2012-01-01T21:53:40

Authorization: DMDS-API DAE1901D-05B5-499E-AD88-F80BA036E346:dmlwZqi0xM2UX82U8A604gMYIcU=

            
StringToSign
GET\n
2012-01-01T21:53:40\n
/API/V1/AD/FILES/VIDEO
            

Code Examples

C#
var apiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var secret = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
var url = "http://api.dmds.com/api/v1/ad/account/availableDestinations?status=online";
var method = "GET";
var date = DateTime.UtcNow.ToString("s");
var hash = "";
using (var hmac = new HMACSHA1(Guid.Parse(secret).ToByteArray()))
{
    var toHash = String.Format
        (
            "{0}\n{1}\n{2}",
            method.ToUpper(), // Uppercase required by API.
            date,
            new Uri(url).AbsolutePath.ToUpper() // Uppercase required by API.
        );
    var encoder = new UTF8Encoding();
    var bytes = hmac.ComputeHash(encoder.GetBytes(toHash));
    hash = Convert.ToBase64String(bytes, Base64FormattingOptions.None);
}
using (var client = new WebClient())
{
    client.Headers["Authorization"] = String.Format("DMDS-API {0}:{1}", apiKey, hash);
    client.Headers["x-dmds-date"] = date;
    client.Headers["Accept"] = "application/json"; // Accept header is required by API.
    var response = client.DownloadString(url);
}
            
PHP
$api_key = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
$secret = 'xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx';
$method = 'GET';
$host = 'api.dmds.com';
$path = '/api/v1/ad/account/availableDestinations';
$query = '?status=online';
$url = 'http://' . $host . $path . $query;
$date = gmdate('Y-m-d\\TG:i:s', time());
$no_dashes_secret = str_replace('-', '', $secret);
$arr = explode('-', chunk_split($no_dashes_secret, 2, '-'));
$reordered_arr = array
    (
        $arr[3],
        $arr[2],
        $arr[1],
        $arr[0],
        $arr[5],
        $arr[4],
        $arr[7],
        $arr[6],
        $arr[8],
        $arr[9],
        $arr[10],
        $arr[11],
        $arr[12],
        $arr[13],
        $arr[14],
        $arr[15]
    );
$key = '';
for ($i = 0; $i < 16; ++$i) {
    $key .= chr(hexdec($reordered_arr[$i]));
}
$to_hash = strtoupper($method) . chr(10) . $date . chr(10) . strtoupper($path);
$hash = base64_encode(hash_hmac('sha1', $to_hash , $key, true));
$headers = array
    (
        'Authorization: ' . 'DMDS-API ' . $api_key . ':' . $hash,
        'x-dmds-date: ' . $date,
        'Accept: ' . 'application/json'
    );
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $url);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
$response = curl_exec($ch);
curl_close($ch);
            
JavaScript
import hmacSha1 from 'crypto-js/hmac-sha1';
import Base64 from 'crypto-js/enc-base64';

const apiKey = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const secret = "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx";
const method = "GET";
const host = 'api.dmds.com';
const path = '/api/v1/ad/account/availableDestinations';
const query = '?status=online';
const url = `http://${host}${path}${query}`;
const date = "2020-12-16T16:57:47";

const transformed = arrayToBase64(guidToBytes(secret));

const message = `${method}\n${date}\n${path.toUpperCase()}`;
const hash = Base64.stringify(hmacSha1(message, Base64.parse(transformed)));

fetch(url, {
  headers: {
    'Authorization': `DMDS-API ${apiKey}:${hash}`,
    'x-dmds-date': date,
    'Accept': 'application/json' // Accept header is required by API.
  }
}).then(response => { });


// https://stackoverflow.com/a/42334410
function arrayToBase64(arr) {
  return btoa(
    new Uint8Array(arr)
      .reduce((data, byte) => data + String.fromCharCode(byte), '')
  );
}

// https://gist.github.com/daboxu/4f1dd0a254326ac2361f8e78f89e97ae
function guidToBytes(guid) {
  var bytes = [];
  guid.split('-').map((number, index) => {
    var bytesInChar = index < 3 ? number.match(/.{1,2}/g).reverse() : number.match(/.{1,2}/g);
    bytesInChar.map((byte) => { bytes.push(parseInt(byte, 16)); })
  });
  return bytes;
}
            

REST Request Signing Problems

When REST request authentication fails, the system responds to the request with an XML error document. The information contained in this error document is meant to help developers diagnose the problem.





API Reference