TaleFin Home
Login

HMAC Authentication

The TaleFin API uses HMAC authentication. This means that every request sent by a Vendor must contain the necessary HMAC headers to be authenticated. This document explains how TaleFin implemented HMAC authentication.

The Relation Between API Tokens, HMAC and Vendors on TaleFin

Vendors need an API Token to use the TaleFin API. These Tokens can be either provided to a Vendor by TaleFin or created by a Vendor via the TaleFin Dashboard. An API Token consists on a name, identifier and a secret, and must be used by the Vendors to compute their HMAC hash for each request.

Generating Tokens Via The Dashboard (For Vendors)

Vendors logged to the Dashboard can generate their own Tokens by clicking on API Token on the left side bar. The screen will show a list of Tokens (if any) and a + button. The Vendor must then click on the + button, type a name and click on Create. The Dashboard will show the identifier and the secret for the new Token. The Vendor must copy the secret because searching for it afterwards is not possible.

Using the Tokens to Build an HMAC Request

All TaleFin API requests require the following headers for HMAC authentication:

headerdescription
"Date"A timestamp
"Content-MD5"A hash of the body of the request
"Content-Type"A Mime Type. Usually "application/json"
"Authorization""HMAC <token_identifier>:<hmac_signature>"

These headers will be verified in the backend once TaleFin receives the request.

The way a request is built vary depending on the language.

Example Usage

The following are examples on how to collect data from the API endpoints using different languages. The token information (name, secret and identifier) gets provided when you create a webhook through the API. Alternatively, contact us on helpdesk@talefin.com and we can create them for you and provide integration assistance. This information is then called when you receive the event application.completed.

Python

1def get_bundle(self, application_id):
2    token = {
3    "name": "token-name",
4    "secret": "token-secret",
5    "identifier": "token-identifier"
6    }
7
8    method      = 'GET'
9    body        = ''
10    root_url    = 'https://banks.talefin.com'
11    path        = '/api/applications/{}/bundle'.format(application_id)
12    timestamp   = datetime.datetime.utcnow().strftime(
13        "%a, %d %b %Y %H:%M:%S GMT")
14    contentType = 'application/json'
15
16    hash = hashlib.md5(body.encode())
17    contentMD5 = b64encode(hash.digest()).decode('utf-8')
18    message_parts = [method, contentMD5, contentType, timestamp, path]
19    message = '\n'.join(message_parts)
20
21    signature = hmac.new(bytes(token['secret'], 'latin-1'),
22                bytes(message, 'latin-1'), digestmod=hashlib.sha256)
23    hmac_base64 = b64encode(signature.digest()).decode('utf-8')
24
25    headers = {
26        'Date': timestamp,
27        'Content-MD5': contentMD5,
28        'Content-Type': contentType,
29        'Authorization': 'HMAC {}:{}'.format(token['identifier'], hmac_base64)
30    }
31
32    request = requests.Request(
33        'GET', '{}{}'.format(root_url, path),
34        data=body, headers=headers)
35    prepped = request.prepare()
36    prepped.headers = headers
37
38    with requests.Session() as session:
39        response = session.send(prepped)
40
41    if response.status_code != 200:
42        print("Bad status code: {}".format(response.status_code))
43        print("Bad status: {}".format(response.text))
44        print(root_url, path)
45        raise()
46
47    print('Retrieved bundle')
48    bundle = response.json()
49    return bundle

Javascript

1async function getFromAPI(path) {
2  const token = {
3    name: 'xxxx',
4    secret: 'xxxx',
5    identifier: 'xxxx',
6  };
7
8  const method = 'GET';
9  const body = '';
10
11  const root = 'https://banks.talefin.com';
12  const timestamp = new Date().toUTCString();
13  const contentType = 'application/json';
14
15  const hash = crypto.createHash('md5');
16  hash.update(body);
17  const contentMD5 = hash.digest('base64');
18
19  const messageParts = [method, contentMD5, contentType, timestamp, path];
20  const message = messageParts.join('\n');
21
22  const hmac = crypto.createHmac('sha256', token.secret);
23  hmac.update(message);
24  const hmacBase64 = hmac.digest('base64');
25
26  const headers = {
27    Date: timestamp,
28    'Content-MD5': contentMD5,
29    'Content-Type': contentType,
30    Authorization: `HMAC ${token.identifier}:${hmacBase64}`,
31  };
32
33  const response = await fetch(root + path, {
34    method,
35    headers,
36    body: body == '' ? null : body,
37  });
38
39  if (!response.ok) {
40    throw new Error(await response.text());
41  }
42
43  return response;
44}

PHP

1public function rawApiCall($identifier, $secret, $method, $path, $body = '')
2{
3    $now = now();
4    $token = [
5        'secret' => $secret,
6        'identifier' => $identifier,
7    ];
8    $root = 'https://banks.talefin.com';
9
10    $timestamp = $now->format('D, d M Y H:i:s')
11    $contentType = 'application/json';
12    $hash = md5($body, true);
13    $contentMD5 = base64_encode($hash);
14    $messageParts = [
15        $method,
16        $contentMD5,
17        $contentType,
18        $timestamp,
19        $path,
20    ];
21    $message = implode("\n", $messageParts);
22    $hash = hash_hmac('sha256', $message, $token['secret'], true);
23    $hmacBase64 = base64_encode($hash);
24    $headers = [
25        'Date' => $timestamp,
26        'Content-MD5' => $contentMD5,
27        'Content-Type' => $contentType,
28        'Authorization' => 'HMAC '.$token['identifier'].':'.$hmacBase64,
29    ];
30    $response = $this->client->request($method, $root.$path, [
31        'verify' => false,
32        'body' => $body ? $body : null,
33        'headers' => $headers,
34        'timeout' => 15
35    ])->getBody()->getContents();
36    $bytes = strlen($body);
37    $secondsToRun = $now->diffInSeconds(now());
38    return $response;
39}

Scala

1import java.security.MessageDigest
2import java.util.Base64
3import javax.crypto.Mac
4import javax.crypto.spec.SecretKeySpec
5import java.nio.charset.StandardCharsets
6
7object CredfinHMAC {
8    def base64(s: Array[Byte]): String = {
9        Base64.getEncoder.encodeToString(s)
10    }
11
12    def md5(s: String): Array[Byte] = {
13        MessageDigest.getInstance("MD5").digest(s.getBytes)
14    }
15
16    def hmac(sharedSecret: String, preHashString: String): Array[Byte] = {
17        vol secret = new SecretKeySpec(sharedSecret.getBytes, "SHA256")
18        val mac = Mac.getInstance("HmacSHA256")
19        mac.init(secret)
20        mac.doFinal(preHashString.getBytes)
21    }
22
23    def generateHeaders(identifier: String, secret: String, timestamp: String, method: String, contentType: String, path: String, body: String) = {
24        var hash: Array[Byte] = md5(body)
25        var contentMd5: String = base64(hash)
26        var messageParts = List(method, contentMd5, contentType, timestamp, path).mkString("\n")
27        var hmacMessage: Array[Byte] = hmac(secret, messageParts)
28        var hmacBase64: String = base64(hmacMessage)
29        var formattedAuth = "HMAC " + identifier + ":" + hmacBase64
30
31        Map("Date"->timestamp, "Content-MD5"->contentMd5, "Content-Type"->contentType, "Authorization"->formattedAuth)
32    }
33
34    def main(args: Array[String]) {
35        /**** ENTER YOUR IDENTIFIER AND SECRET HERE ****/
36        var identifier : String = "50m3cr3df1n1d3n71f13r"
37        var secret : String = "50m3cr3d175up3r53cr37k3y"
38        /**** PLEASE INSERT A TIMESTAMP HERE ****/
39        var timestamp : String = "Fri, 04 Nov 2022 07:33:44 GMT"
40        /**** SET THE HTTP METHOD BEING USED ****/
41        var method: String = "POST"
42        /**** SET THE HTTP CONTENT TYPE BEING USED ****/
43        var contentType : String = "application/json"
44        /**** SET THE HTTP ENDPOINT BEING USED ****/
45        var path: String = "/api/v1/application/1111"
46        /**** SET THE HTTP BODY BEING USED ****/
47        var body : String = ""
48
49        var headers = generateHeaders(identifier, secret, timestamp, method, contentType, path, body)
50
51        print("Headers")
52        print("\nDate: " + headers.get("Date"))
53        print("\nContent-MD5: " + headers.get("Content-MD5"))
54        print("\nContent-Type: " + headers.get("Content-Type"))
55        print("\nAuthorization: " + headers.get("Authorization"))
56    }
57}

C#

1using RestSharp;
2using System;
3using System.Collections.Generic;
4using System.Net.Http;
5using System.Net.Http.Headers;
6using System.Reflection;
7using System.Text;
8using System.Threading;
9using System.Threading.Tasks;
10
11namespace CredFinApiTest
12{
13    /// <summary>
14    /// Both the .NET HttpClient and Restsharp are very strict in not allowing content headers to be added to GET requests.  To get around this we need to add a handler that can circumvent the 
15    /// restrictions and add the headers we need for autentication/authorization.
16    /// </summary>
17    public class AddContentTypeHeaders : DelegatingHandler
18    {
19        private string _md5;
20
21        public AddContentTypeHeaders(string md5)
22        {
23            _md5 = md5;
24            InnerHandler = new HttpClientHandler();
25        }
26
27        /// <summary>
28        /// Remove the headers we want to add from the invalid header list.
29        /// </summary>
30        private void RemoveInvalidHeadera(HttpRequestMessage request)
31        {
32            FieldInfo invalidHeadersField =
33                typeof(HttpHeaders).GetField("invalidHeaders", BindingFlags.NonPublic | BindingFlags.Instance) ??   // System.Net.Http v2.2+
34                typeof(HttpHeaders).GetField("_invalidHeaders", BindingFlags.NonPublic | BindingFlags.Instance)     // System.Net.Http before v2.2
35            ;
36            HashSet<string> invalidHeaders = (HashSet<string>)invalidHeadersField.GetValue(request.Headers);
37            invalidHeaders.Remove("Content-Type");
38            invalidHeaders.Remove("Content-MD5");
39        }
40
41        /// <summary>
42        /// Add the content type headers required for authorisation/authentication
43        /// </summary>
44        private void AddContentHeaders(HttpRequestMessage request)
45        {
46            request.Headers.Add("Content-Type", "application/json");
47            request.Headers.Add("Content-MD5", _md5);
48        }
49
50        protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken token)
51        {
52            RemoveInvalidHeadera(request);
53            AddContentHeaders(request);
54
55            return await base.SendAsync(request, token); //Call next handler in pipeline
56        }
57    }
58
59    internal class Program
60    {
61        public static void Main()
62        {
63            // Set your identifier and secrets
64            string identifier = "xxxx";
65            string secret = "xxxx";
66
67            // Set your query path
68            string path = "/api/v1/analyses/44834/report/pdf";
69
70            //Make the call to get the data
71            var response = Program.GetMethod(identifier, secret, path);
72
73            //Write the response to a file
74            System.IO.File.WriteAllBytes(@"d:\temp\credfin.pdf", response);
75        }
76
77        private static byte[] GetMethod(string identifier, string secret, string path)
78        {
79            string timestamp = DateTime.UtcNow.ToString("ddd, dd MMM yyyy HH:mm:ss") + " GMT";
80            string method = "GET";
81            string contentType = "application/json";
82            string body = "";
83
84            byte[] md5 = Program.Md5(body);
85            string contentMd5 = Program.Base64(md5);
86            string[] messagePartsList = { method, contentMd5, contentType, timestamp, path };
87            string messageParts = string.Join("\n", messagePartsList);
88            byte[] hmacMessage = Program.Hmac(secret, messageParts);
89            string hmacBase64 = Program.Base64(hmacMessage);
90            string authorization = identifier + ":" + hmacBase64;
91
92            string baseUrl = "https://banks-staging.talefin.com";
93            string remoteUrl = baseUrl + path;
94
95            //Add the handler to the client, so the correct headers will be added when making the request.
96            var options = new RestClientOptions(baseUrl)
97            {
98                ConfigureMessageHandler = handler =>
99                    new AddContentTypeHeaders(contentMd5)
100            };
101            var client = new RestClient(options);
102
103            //Create the request.  The Content headers will be added by the handler.  
104            var request = new RestRequest(path, RestSharp.Method.Get);
105            request.AddHeader("Date", timestamp);
106            request.AddHeader("Authorization", "HMAC " + authorization);
107
108            //Now download the report
109            return client.DownloadData(request);
110        }
111
112        private static byte[] Hmac(string secret, string message)
113        {
114            ASCIIEncoding encoding = new ASCIIEncoding();
115            byte[] keyBytes = encoding.GetBytes(secret);
116            byte[] messageBytes = encoding.GetBytes(message);
117            System.Security.Cryptography.HMACSHA256 cryptographer = new System.Security.Cryptography.HMACSHA256(keyBytes);
118
119            return cryptographer.ComputeHash(messageBytes);
120        }
121
122        private static string Base64(byte[] bytes)
123        {
124            return System.Convert.ToBase64String(bytes);
125        }
126
127        private static byte[] Md5(string strInput)
128        {
129            System.Security.Cryptography.MD5 md5 = System.Security.Cryptography.MD5.Create();
130            byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(strInput);
131            return md5.ComputeHash(inputBytes);
132        }
133    }
134}

Start Now

FDATA
TaleFin Home© Copyright 2024 TaleFin Australia Pty Ltd. ABN 71 632 551 770
Solutions