Authentication

When authenticating, make sure all GET/POST operations have correctly added the Authorization header to the HTTP request using a custom "ntc" authentication scheme.

Cervey will provide an Application ID and API Key to each entity requesting access to the Claims API. The Application ID will uniquely identify that entity in each API call. The API Key is a shared secret that should be stored securely, which will allow Cervey to verify that the caller of the API is who they claim to be.

When we receive an API request, we will examine the authentication header and the included hash of several values, which will also be passed in with the request separate from their combined hashed value. We will perform the same hashing process using the values passed in the request and the shared secret (API Key). If the hashed value sent in matches the hashed value we calculate, the API call will be validated.

The authentication header value should be made up of the following values, in order and separated by colons:

  1. Application ID
  2. Request Signature
    1. This value is created by concatenating the Application ID, request HTTP method, request URI, request timestamp, and nonce values together, then generating an HMACSHA256 hash of the concatenated value using the secret API Key. The hashed value should then be base64 encoded.
  3. Nonce
  4. Request

An encoded header would appear something like:

    ntc 0F4575AB5CDE496E96A2C8CEC4AEFE57C82C007A29974A8E8D4335F70CC140F9:9p+gbTsvF3hbdtySQ5GIUxDQ1pjB3atEIJ35dBpwfpM=:7ca9e83609f74bdcbf3199d6c410fff5:1527025062

Code Examples:

            string AppID = "0F4575AB5CDE496E96A2C8CEC4AEFE57C82C007A29974A8E8D4335F70CC140F9";
            string APIKey = "RsMhYGkrSq9ty7+t2vG3kdcJSLbDbrwn13+ldP6KoXY=";

            string requestUri = string.Format("{0}{1}", APIBaseAddress, "company");
            requestUri = System.Web.HttpUtility.UrlEncode(requestUri.ToLower());
            string requestHttpMethod = "GET";

            // Calculate UNIX time
            DateTime epochStart = new DateTime(1970, 01, 01, 0, 0, 0, 0, DateTimeKind.Utc);
            TimeSpan timeSpan = DateTime.UtcNow - epochStart;
            string requestTimeStamp = Convert.ToUInt64(timeSpan.TotalSeconds).ToString();

            // Create random nonce for each request
            string nonce = Guid.NewGuid().ToString("N");

            // Create signature by concatenating AppID, requestHttpMethod, requestUri, requestTimeStamp, and nonce
            string signatureRawData = String.Format("{0}{1}{2}{3}{4}", AppID, requestHttpMethod, requestUri, requestTimeStamp, nonce);

            // Get secret key bytes
            var secretKeyByteArray = Convert.FromBase64String(APIKey);

            // Get bytes from signature string data
            byte[] signature = Encoding.UTF8.GetBytes(signatureRawData);

            // Use secret key as the key parameter for the HMACSHA256 constructor
            using (HMACSHA256 hmac = new HMACSHA256(secretKeyByteArray))
            {
                // Hash the signature using HMACSHA256
                byte[] signatureBytes = hmac.ComputeHash(signature);

                // Convert hashed signature bytes to base64 to send to API end point
                string requestSignatureBase64String = Convert.ToBase64String(signatureBytes);

                // The header value consists of colon separated values of AppID, the base64 signature, nonce, and request time stamp
                AuthenticationHeaderValue headerVal = new AuthenticationHeaderValue(
                    "ntc",
                    string.Format("{0}:{1}:{2}:{3}", AppID, requestSignatureBase64String, nonce, requestTimeStamp));
            }
        
            // Postman Pre-request Script

            /* Pre-requisite
            ==================
            1) Create an Environment in Postman(if you don't already have one) and enable it for your request
                a) Add APIKey and ClientID variables to the environment with correct values 

            function S4() {
                return (((1+Math.random())*0x10000)|0).toString(16).substring(1); 
            }

            function GetNonce() {
                return (S4() + S4() + S4()+ S4() + S4() + S4() + S4()+ S4()).toLowerCase();
            }

            function GetTimeStamp() {
                var d = new Date();
                return Math.round(d.getTime() / 1000);
            }

            function getAuthHeader(requestUrl, httpMethod) {
                var APIKEY = pm.environment.get('APIKey');
                var CLIENTID = pm.environment.get('ClientID');
         
                var requestTimeStamp = GetTimeStamp();
                var nonce = GetNonce();
                requestUrl=encodeURIComponent(requestUrl);
                var signatureRawData = [CLIENTID, httpMethod, requestUrl.toLowerCase(), 
                    requestTimeStamp, nonce].join("");
                var key = CryptoJS.enc.Base64.parse(APIKEY);
                var hash = CryptoJS.HmacSHA256(signatureRawData, key);
                var hashInBase64 = CryptoJS.enc.Base64.stringify(hash);

                var header = [CLIENTID, hashInBase64, nonce, requestTimeStamp].join(":");

                return "ntc "+ header;
            }

            var {Property} = require('postman-collection');
            const substitutedUrl = Property.replaceSubstitutions(request.url, pm.variables.toObject());

            pm.request.headers.add({key: 'Authorization', value: getAuthHeader(substitutedUrl, request['method']) });
        
            import java.net.URLEncoder;
            import java.nio.charset.StandardCharsets;
            import java.util.Base64;
            import java.util.UUID;
            import java.net.http.HttpRequest;
            import java.net.http.HttpClient;
            import java.net.URI;
            import java.net.http.HttpResponse;
            import javax.crypto.Mac;
            import javax.crypto.spec.SecretKeySpec;
            import java.util.concurrent.TimeUnit;
            import java.util.Date;

            public class Main {

                public static final String BaseUrl = "https://claimsapi.cervey.com";

                //-- API credentials need to be updated here
                private static final String apiKey = ""; // Update this
                private static final String apiClientID = ""; // Update this

                // Modify the signature and add the Authorization header to the request
                public static String addAuthenticationHeader(HttpRequest.Builder requestBuilder, String absoluteUri, String httpMethod) throws Exception {
                    //-- Encodes the URL and ensures it's lowercase to maintain consistent formatting.
                    String encodedURL = URLEncoder.encode(absoluteUri.toLowerCase(), StandardCharsets.UTF_8.toString());
                    encodedURL = encodedURL.toLowerCase();

                    //-- Calculates a timestamp based on the current time, starting from January 1, 1970.
                    long requestTimeStamp = TimeUnit.MILLISECONDS.toSeconds(new Date().getTime());

                    //-- Generates a unique identifier (nonce) for each request to prevent replay attacks.
                    String nonce = UUID.randomUUID().toString().replace("-", "");

                    //-- Combines key elements into a raw string that will be hashed.
                    String signatureRawData = String.format("%s%s%s%s%s", apiClientID, httpMethod, encodedURL, requestTimeStamp, nonce);

                    //-- Converts the API key from base64 into bytes.
                    byte[] secretKeyByteArray = Base64.getDecoder().decode(apiKey);

                    //-- Hashes the signature data using HMACSHA256 with the secret key.
                    Mac hmac = Mac.getInstance("HmacSHA256");
                    SecretKeySpec secretKeySpec = new SecretKeySpec(secretKeyByteArray, "HmacSHA256");
                    hmac.init(secretKeySpec);

                    //-- Creates the final signature as a base64 string.
                    byte[] signatureBytes = hmac.doFinal(signatureRawData.getBytes(StandardCharsets.UTF_8));
                    String requestSignatureBase64String = Base64.getEncoder().encodeToString(signatureBytes);

                    //-- Adds the Authorization header with the appID, signature, nonce, and timestamp.
                    String authorizationHeader = String.format("ntc %s:%s:%s:%s", apiClientID, requestSignatureBase64String, nonce, requestTimeStamp);
                    requestBuilder.header("Authorization", authorizationHeader);

                    return authorizationHeader;  // Return the auth header so we can print it later
                }

                // Send a request to the Company API endpoint
                public static HttpResponse getCompany(HttpClient httpClient) throws Exception {
                    String absoluteUri = BaseUrl + "/api/company"; // Example endpoint

                    // Create the request builder
                    HttpRequest.Builder requestBuilder = HttpRequest.newBuilder().uri(URI.create(absoluteUri)).GET();

                    // Add authentication headers to the request
                    String authHeader = addAuthenticationHeader(requestBuilder, absoluteUri.toLowerCase(), "GET");

                    // Build the request and send it
                    HttpRequest request = requestBuilder.build();
                    HttpResponse response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

                    // Print the response body
                    System.out.println("Status Code: " + response.statusCode());
                    System.out.println("Response Body: " + response.body());

                    // Print the Authorization header
                    System.out.println("Authorization Header: " + authHeader);

                    return response;
                }

                public static void main(String[] args) throws Exception {
                    HttpClient httpClient = HttpClient.newHttpClient();
                    getCompany(httpClient);
                }
            }
        
            import base64
            import hashlib
            import hmac
            import time
            import uuid
            import urllib.parse
            import requests

            # API credentials need to be updated here
            api_key = ""  # Update this
            api_client_id = ""  # Update this
            base_url = "https://claimsapi.cervey.com"

            def add_authentication_header(url, http_method):
                # URL encode and ensure it's lowercase
                encoded_url = urllib.parse.quote(url.lower(), safe='').lower()

                # Calculate timestamp
                request_timestamp = int(time.time())

                # Generate nonce
                nonce = uuid.uuid4().hex

                # Combine key elements into raw data string
                signature_raw_data = f"{api_client_id}{http_method}{encoded_url}{request_timestamp}{nonce}"

                # Convert API key from base64 to bytes
                secret_key_byte_array = base64.b64decode(api_key)

                # Hash the signature data using HMAC-SHA256 with the secret key
                hmac_obj = hmac.new(secret_key_byte_array, signature_raw_data.encode('utf-8'), hashlib.sha256)
                request_signature_base64_string = base64.b64encode(hmac_obj.digest()).decode('utf-8')

                # Create Authorization header
                authorization_header = f"ntc {api_client_id}:{request_signature_base64_string}:{nonce}:{request_timestamp}"

                return {"Authorization": authorization_header}, authorization_header  # Return header dict and auth header string

            def get_company():
                # Construct the absolute URL
                absolute_url = f"{base_url}/api/company"

                # Add authentication header
                headers, auth_header = add_authentication_header(absolute_url, "GET")

                # Send GET request
                response = requests.get(absolute_url, headers=headers)

                # Print status code and response body
                print(f"Status Code: {response.status_code}")
                print(f"Response Body: {response.text}")

                # Print the Authorization header
                print(f"Authorization Header: {auth_header}")

            if __name__ == "__main__":
                get_company()