> This page is for Taurus PROTECT, version v3.38.
> For other versions, use one of these documentation indexes:
> - v3.58 (default): https://taurushq.ferndocs.com/protect-capital/v3.58/llms.txt
> - v3.56: https://taurushq.ferndocs.com/protect-capital/v3.56/llms.txt
> - v3.54: https://taurushq.ferndocs.com/protect-capital/v3.54/llms.txt
> - v3.52: https://taurushq.ferndocs.com/protect-capital/v3.52/llms.txt
> - v3.50: https://taurushq.ferndocs.com/protect-capital/v3.50/llms.txt
> - v3.48: https://taurushq.ferndocs.com/protect-capital/v3.48/llms.txt
> - v3.46: https://taurushq.ferndocs.com/protect-capital/v3.46/llms.txt
> - v3.44: https://taurushq.ferndocs.com/protect-capital/v3.44/llms.txt
> - v3.42: https://taurushq.ferndocs.com/protect-capital/v3.42/llms.txt
> - v3.40: https://taurushq.ferndocs.com/protect-capital/v3.40/llms.txt
> - v3.38: https://taurushq.ferndocs.com/protect-capital/v3.38/llms.txt

> For clean Markdown of any page, append .md to the page URL.
> For a complete documentation index, see https://taurushq.ferndocs.com/llms.txt.
> For AI client integration (Claude Code, Cursor, etc.), connect to the MCP server at https://taurushq.ferndocs.com/_mcp/server.

# Hmac Based Authentication

This method provides integrity in addition to authenticity checks, as each request is signed with a secret key. The key does not expire and is valid until revoked.\
As the secret key is exclusively used for API authentication it is called`ApiKey`.

An `ApiKey` can be generated for any Taurus-PROTECT or Taurus-CAPITAL user, and can be obtained via the Graphical User Interface as described below.

This shared secret is then used to sign every API request.

## Obtaining an ApiKey via the UI

1. Log in as a user with **user manager** role privileges.
2. Navigate to the `Users`menu on the left navigation bar.
3. Open the details of the `User` you wish to generate an `ApiKey` for, and expand the **> API keys** section.

![](/_fern-img/c4de58c3f05abdd9c36e3173c56006b07b553f7147bb0829daae2e3c77e7bdf9.webp)

4. Click on **Generate new API key**, which triggers a change request to create an `ApiKey` for this `User`, which needs approval by an additional user with **user manager** role privileges.
5. Log in with a different **user manager**
6. Navigate to the `Changes -> To Validate` menu and tab
7. Approve the change request

![](/_fern-img/efa2ab5991a57290ec66820eb88b61742589348cec8fab10046d46a052e11a6b.webp)

8. You can now go back to the `User` details page where it will display an `ApiKey`

![](/_fern-img/0b862ec7e918ca2edec879f0dad3cb386bb65ee7b329ff688cba5d7d22753059.webp)

9. Click on the eye icon next to the API key to **reveal** the secret

> **Warning**
>
> **Careful**
>
> Revealing a secret can be done only once. If the secret is lost, a new **ApiKey** needs to be created and the **ApiKey** corresponding to the lost secret should be revoked.

![](/_fern-img/aafef6ce66b46eb0ce332ca1ac8c5cea2bfd0ee833b48355ab100c87ce8b3fc3.webp)

10. Both strings, the **Id** and the **Secret**, are required for successful HMAC authentication. Make sure you store these in a safe place.

## Using the HMAC-authentication method

The HMAC-authentication is based on cryptographic signatures. Instead of providing a JWT that represents a “session” when doing a request, the `User` signs every API request.

1. To generate the signature of a request the following information is required for  the authentication header:

* a **prefix**, identifying the protocol version. Currently only `TPV1` is supported.
* the `User`’s **ApiKey identifier** (The unique Id - see section above)
* a **nonce**,  a string that needs to be different with every request (usually a UUID)
* a **timestamp**, as a unix epoch timestamp in milliseconds
* the **HTTP method** of the request (e.g. `POST`)
* the **HTTP host** - the fully qualified host name the request is being sent to (e.g. `tg-validatord-instance.t-dx.com`)
* the **path** of the request (e.g. `/api/rest/v1/blockchains`)
* the **query** string of the request (e.g. `?query=BTC`)
* the **content type** of the request (usually `application/json`)
* the **body** of the request as a string (empty for `GET` requests, usually JSON for a `POST` or `PUT` request e.g. `{"query":"BTC"}`)

The strings above are concatenated into a single string, in the order listed. The individual string components are separated by spaces. Empty elements are not included.

**`Sample script to generate the concatenated string (in Python)`**

```Text Sample script to generate the concatenated string (in Python)
parts = [
    "TPV1",
    self.key,
    nonce,
    timestamp,
    method,
    host,
    path,
    query,
    content_type
]
parts = [p for p in parts if p and p != ""]
msg = bytes(' '.join(parts), 'utf-8')
if body:
    msg += bytes(' ','utf-8')
    msg += body
```

2. This is then signed with Hash-based message authentication code (HMAC), using the `ApiKey` secret for encryption.

**`Sample script to generate the signature (in Python)`**

```Text Sample script to generate the signature (in Python)
signatureBytes = hmac.new(bytes.fromhex(self.secret), msg = msg, digestmod = hashlib.sha256).digest()
```

3. The signature is then encoded in base64

**`Sample script to generate the base64 string (in Python)`**

```Text Sample script to generate the base64 string (in Python)
signature = base64.b64encode(signatureBytes).decode('ascii')
```

4. The Authorization header has the following form:

`TPV1-HMAC-SHA256 ApiKey={} Nonce={} Timestamp={} Signature={}`

Where **TPV1-HMAC-SHA256** is a fixed string identifying the protocol. **ApiKey, Nonce** and **Timestamp** have to match the values that were signed in 1. - 3. above.

**`Sample script to generate the header (in Python)`**

```Text Sample script to generate the header (in Python)
auth_header = 'TPV1-HMAC-SHA256 ApiKey={} Nonce={} Timestamp={} Signature={}'.format(self.key, nonce, timestamp, signature)
req.headers['Authorization'] = auth_header
```

5. The request can now be sent together with the Authorization header above.

## Examples

Here we provide 2 examples of this method.

The first example is a python script. This script can be started to launch a small proxy server that can receive a request, sign it on the fly, and forward it to the intended server. It demonstrates the signature mechanism based on the request’s content and the User’s `ApiKey` details

**`Python Proxy Server`**

```Text Python Proxy Server
# This script demos an authentication proxy server.
# The proxy server listens on a specified local port and forwards all requests
# to the configured URL, whilst adding an authorization header based on the
# supplied API key.
#
# Usage example (illustrative):
#
# > python3 auth_proxy.py --port 9000 --destination https://tg-validatord-cockroach.t-dx.com --key f9553d35-83ef-4796-a3ea-eeb5558462df --secret bb38...a19f
#
# This software is provided for demonstration purposes only. As documented by the
# Python3 documentation ( https://docs.python.org/3/library/http.server.html )
# the HTTPServer class is not recommended for use in production systems.
# Production systems should provide authorization as part of request creation,
# to ensure the authenticity of signed requests.

import base64
import hashlib
import hmac
import requests
import time
import uuid
from functools import partial
from http.server import BaseHTTPRequestHandler,HTTPServer
from socketserver import ThreadingMixIn
from urllib.parse import urlparse, urlunparse


class Server(BaseHTTPRequestHandler):
    def __init__(self, destination, key, secret, *args, **kwargs):
        self.destination = destination
        self.key = key
        self.secret = secret
        super().__init__(*args, **kwargs)

    def do_GET(self):
        self._handle_request("GET", requests.get)

    def do_DELETE(self):
        self._handle_request("DELETE", requests.delete)

    def do_POST(self):
        self._handle_request("POST", requests.post)

    def do_PUT(self):
        self._handle_request("PUT", requests.put)

    def do_PATCH(self):
        self._handle_request("PATCH", requests.patch)

    def _handle_request(self, method, requests_func):
        url = urlparse(self.destination + self.path)
        content_type = self.headers["content-type"]
        body = self.rfile.read(int(self.headers["content-length"])) if self.headers["content-length"] else None

        headers = dict(self.headers)
        headers['Host'] = url.netloc
        headers['Authorization'] = self._generate_auth_header(method, url.netloc, url.path, url.query, content_type, body)

        resp = requests_func(urlunparse(url), data=body, headers=headers)

        self.send_response(resp.status_code)
        for key in resp.headers:
            # requests package decodes response, this changes content length and encoding
            if key == 'Content-Length':
                self.send_header('Content-Length', "{}".format(len(resp.content)))
            elif key == 'Content-Encoding':
                pass
            elif key == 'Transfer-Encoding':
                pass
            else:
                self.send_header(key, resp.headers[key])
        self.end_headers()
        self.wfile.write(resp.content)
        resp.close()

    def _generate_auth_header(self, method, host, path, query, content_type, body):
        nonce = str(uuid.uuid4())
        timestamp = str(int(time.time() * 1000))
        parts = [
            "TPV1",
            self.key,
            nonce,
            timestamp,
            method,
            host,
            path,
            query,
            content_type
        ]
        parts = [p for p in parts if p and p != ""]
        msg = bytes(' '.join(parts), 'utf-8')
        if body:
            msg += bytes(' ','utf-8')
            msg += body

        signatureBytes = hmac.new(bytes.fromhex(self.secret), msg = msg, digestmod = hashlib.sha256).digest()
        signature = base64.b64encode(signatureBytes).decode('ascii')

        header = 'TPV1-HMAC-SHA256 ApiKey={} Nonce={} Timestamp={} Signature={}'.format(self.key, nonce, timestamp, signature)
        return header


def run(port, destination, key, secret):
    class ThreadedHTTPServer(ThreadingMixIn, HTTPServer):
        """Handle requests in a separate thread."""

    httpd = ThreadedHTTPServer(('', port), partial(Server, destination, key, secret))

    print('Starting proxy on port {} redirecting to {} ...'.format(port, destination))
    httpd.serve_forever()

if __name__ == "__main__":
    import argparse

    argParser = argparse.ArgumentParser()
    argParser.add_argument("-p", "--port",        nargs='?', const=9000, type=int, help="local port to listen on")
    argParser.add_argument("-d", "--destination", nargs=1, required=True, help="destination host to forward to")
    argParser.add_argument("-k", "--key",         nargs=1, required=True, help="API key ID (uuid)")
    argParser.add_argument("-s", "--secret",      nargs=1, required=True, help="API key secret (hex-encoded)")
    args = argParser.parse_args()

    destination = args.destination[0]
    key = args.key[0]
    secret = args.secret[0]

    run(args.port, destination, key, secret)

```

The second example is a `Pre-request Script` that can be used in the `Postman` [tool](https://www.postman.com) (Graphical UI tool to call APIs). Place the following script in the `Pre-request Script` section of your Postman Taurus-PROTECT setup, and make sure to store a valid `apiKey` and `apiSecret` in the Postman Vault (requires a recent version of Postman).

**`Postman Pre-request script`**

```Text Postman Pre-request script
var url = require('url');
var uuid = require('uuid');
var CryptoJS = require('crypto-js')
var { Property } = require('postman-collection');

var apiKey = await pm.vault.get("apiKey");
var apiSecret = await pm.vault.get("apiSecret");

const authorizationScheme = 'TPV1-HMAC-SHA256';

function computeHmac(message, key_hex) {
    enc = CryptoJS.HmacSHA256(message, CryptoJS.enc.Hex.parse(key_hex));
    return CryptoJS.enc.Base64.stringify(enc);
}

function newNonce() {
    return uuid.v4();
}

function calculateAuthHeader(req) {
    let urlExpanded = Property.replaceSubstitutions(req.url.toString(), pm.variables.toObject());
    let parsedUrl = url.parse(urlExpanded);
    let hostname = parsedUrl.hostname;
    let port = parsedUrl.port;
    if ((port !== "") && (port !== null) && (port !== undefined)) {
        hostname = hostname + ":" + port;
    }
    let path = parsedUrl.pathname;
    let queryString = parsedUrl.query;
    let method = req.method;
    let dateStamp = Date.now().toString();
    let nonce = newNonce();
    let body = req.body.toString();
    let contentType = ''
    if (method === "POST" || method === "PUT") {
        contentType = 'application/json';
    }

    items = [
        "TPV1",
        apiKey,
        nonce,
        dateStamp,
        method,
        hostname,
        path,
        queryString,
        contentType,
        body,
    ];
    
    let payload = "";
    for (item of items) {
         if ((item !== "") && (item !== undefined) && (item !== null)) {
            if (payload.length > 0) {
                payload = payload + " ";    
            }
            payload = payload + item;
        }
    }

    sig = computeHmac(payload, apiSecret);
    return `${authorizationScheme} ApiKey=${apiKey} Nonce=${nonce} Timestamp=${dateStamp} Signature=${sig}`;
}

pm.request.headers.add({
    key: 'Authorization',
    value: calculateAuthHeader(pm.request)
});
```