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:
header | description |
---|---|
"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}