Example code

If you are accessing the zeuz base API directly, you can adapt the following example code for your own purposes.

Before you begin

Before you start to adapt the example code for your own purposes, ensure that you have read the following pages, so you know what you need to set up:

This example code covers two of the tasks from API authentication overview: 3. Log in to zeuz and generate a session key and 4. Generate a sign-hash to attach to your API requests as well as additional functionality, as listed below:

  • Define data structures
    • Example 1: Define data structures.
  • Log in and generate a session key
    • Example 2: Generate a nonce and timestamp, and formulate a cURL request to the auth_login endpoint.
    • Example 3: Log in to zeuz by sending the cURL request formulated in Example 2 and generate a session key.
  • Generate a sign-hash, check the logged in session is valid, and send a request to an API endpoint
    • Example 4: Generate a sign-hash, send a JSON packet to the auth_check API endpoint, and parse the return value to see if the logged in session is valid or invalid. If it’s valid, send a new cURL request to a zeuz API endpoint.

These examples use OpenSSL for hashing with secure hash algorithm SHA3 and base64 encoding, and the libcurl library for HTTP requests.

Note: You can also see separate example snippets in C++ for the two main tasks in API login and Sign-hash generation.

Examples

Example 1: Define data structures (C++)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
struct LoginData {
   // SessionId: Unique identifier for a zeuz API session
   // Valid for 24 hours after generation by the auth_login endpoint
   std::string SessionId;

   // SessionKey: Value used to verify your identity with the zeuz API
   // Tied to the SessionId
   std::string SessionKey;

   // ValidThru: Date/time when the session expires
   long ValidThru;
};

Example 2: Generate a nonce and timestamp, and formulate a cURL request to the auth_login endpoint (C++)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
#include <chrono>
#include <cstring>
#include <string>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <openssl/buffer.h>
#include <unistd.h>
#include <curl/curl.h>

// Generate a nonce of a specified length
std::string generate_nonce(const int len) {
   std::string tmp_s;
   static const char alphanum[] =
      "0123456789"
      "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
      "abcdefghijklmnopqrstuvwxyz";

   srand( (unsigned) time(NULL) * getpid());
   tmp_s.reserve(len);
   for (int i = 0; i < len; ++i)
       tmp_s += alphanum[rand() % (sizeof(alphanum) - 1)];
   return tmp_s;
}
// Generate a zeuz timestamp for the current time
// See doc.zeuz.io/docs/time-handling for more information
long zeuz_now() {
   auto now = std::chrono::system_clock::now();
   auto now_ms = std::chrono::time_point_cast<std::chrono::seconds>(now);
   auto epoch = now_ms.time_since_epoch();
   return (epoch.count() + 2208988800) * 1000 * 1000;
}

// Encode the input string using base64
std::string base64_encode(unsigned char * value, unsigned int length) {
   BIO *bmem, *b64;
   BUF_MEM *bptr;

   b64 = BIO_new(BIO_f_base64());
   bmem = BIO_new(BIO_s_mem());
   b64 = BIO_push(b64, bmem);
   BIO_write(b64, value, length);
   BIO_flush(b64);
   BIO_get_mem_ptr(b64, &bptr);

   char *buff = (char *)malloc(bptr->length);
   std::memcpy(buff, bptr->data, bptr->length-1);
   buff[bptr->length-1] = 0;

   BIO_free_all(b64);

   auto ret = std::string(buff);
   free(buff);
   return ret;
}

// Hash the input string using SHA3-256
void sha3_256_hash(const char * value, int length, unsigned char ** output, unsigned int * output_length) {
   EVP_MD_CTX *ctx = EVP_MD_CTX_new();
   EVP_MD_CTX_init(ctx);
   EVP_DigestInit(ctx, EVP_sha3_256());
   EVP_DigestUpdate(ctx, value, length);
   *output = (unsigned char *)OPENSSL_malloc(EVP_MD_size(EVP_sha3_256()));
   EVP_DigestFinal(ctx, *output, output_length);
   EVP_MD_CTX_free(ctx);
}

// Hash the input string using SHA3-256, then encode it using base64 
// and return the value
std::string zeuz_hash(std::string value) {
   unsigned char * hash;
   unsigned int hash_length;
   sha3_256_hash(value.c_str(), value.length(), &hash, &hash_length);
   std::string base64_hash = base64_encode(hash, hash_length);
   OPENSSL_free(hash);
   return base64_hash;
}

// cURL calls a function to process the response
// See: curl.se/libcurl/c/CURLOPT_WRITEFUNCTION.html
size_t read_curl_response(const char *in, size_t size, size_t num, std::string *out) {
   const std::size_t totalBytes(size * num);
   out->append(in, totalBytes);
   return totalBytes;
}

// Formulate a POST cURL request to the auth_login endpoint
// and return the response as a std::string
std::string post_request(std::string url, std::string post_body) {
   auto curl = curl_easy_init();
   // Set the options for the cURL request
   // Set the auth_login endpoint in CURLOPT_URL
   // You can use curl_easy_setopt to make a POST request to any
   // zeuz API endpoint
   // See: curl.se/libcurl/c/curl_easy_setopt.html
   // See: doc.zeuz.io/reference
   curl_easy_setopt(curl, CURLOPT_URL, url.c_str());
   curl_easy_setopt(curl, CURLOPT_POSTFIELDS, post_body.c_str());

   std::string response_data;
   curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, read_curl_response);
   curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response_data);

   curl_easy_perform(curl);

   long http_code(0);
   curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &http_code);

   // Clean up when the request is complete
   curl_easy_cleanup(curl);

   // The zeuz API returns custom error codes in the error field of the 
   // JSON response. Handle response data directly in functions
   return response_data;
}

Example 3: Log in to zeuz by sending the cURL request formulated in Example 2 and generate a session key (C++)

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
#include "login.h"
#include <libscrypt.h>
#include <jsoncpp/json/writer.h>
#include <jsoncpp/json/reader.h>

// Generate a password-hash
// login: Developer username or API key
// password: Developer password or API password
std::string LoginFunction::generate_pwhash(std::string login, std::string password) {
   std::string salt = "zeuz" + login;
   uint8_t hash[32] = { 0 };

   // This example uses the libscrypt-dev library. To install it:
   // sudo apt-get install libscrypt-dev
   libscrypt_scrypt(
       reinterpret_cast<const uint8_t*>(&(password.c_str())[0]),
       password.length(),
       reinterpret_cast<const uint8_t*>(&(salt.c_str())[0]),
       salt.length(),
       1024,
       8,
       1,
       hash,
       32);
   return "a" + base64_encode((unsigned char *) hash, 32);
}

// Format the login data in JSON format
// See doc.zeuz.io/reference#auth_login for details
std::string format_login_data_to_json_payload(long time, std::string hash, std::string login, std::string nonce) {
   Json::Value data;
   Json::FastWriter writer;
   data["Time"] = Json::Int64(time);
   data["Data"]["Hash"] = hash;
   data["Data"]["IsApi"] = false;
   data["Data"]["IsUser"] = false;
   data["Data"]["Login"] = login;
   data["Data"]["Nonce"] = nonce;
   data["Data"]["Time"] = Json::Int64(time);
   auto json = writer.write(data);
}

// Log in to the zeuz API
LoginData LoginFunction::login(std::string login, std::string password) {

	// Generate a nonce of a specified length
   auto nonce = generate_nonce(10);

	// Generate a zeuz timestamp for the current time
  // See doc.zeuz.io/docs/time-handling for more information
   auto now = zeuz_now(); 

	// Generate a password-hash
   auto pwhash = generate_pwhash(login, password);

	// Combine the nonce, timestamp and password-hash into a request-hash
   auto request_hash = zeuz_hash(nonce + std::to_string(now) + pwhash);

   // Format the data into a JSON payload
   // This example uses JsonCpp
   auto data = format_login_data_to_json_payload(now, request_hash, login, nonce);

   // Send the data in a HTTP POST request body to the zeuz API 
   // auth_login endpoint.
   auto response = post_request("https://zcp.zeuz.io/api/v1/auth_login", data);

   // Parse the JSON response.
   Json::Reader reader;
   Json::Value response_json;
   if (!reader.parse(response, response_json)) {
       // Throw an error if the parse fails
       // Note: In production code, use a C++ exception instead
       throw std::string("could not parse response data ( " + response + ")");
   }

   // Check whether the error field is set
   auto error = response_json["Error"].asString();
   if (!error.empty()) {

      // If the error starts with request_expired, 
      // the generated timestamp is too old
      if (error.rfind("request_expired", 0) == 0) {
          // Note: In production code you could retry the login,
          // or ensure the system clock is correct
          throw std::string("verify system clock or retry request");
      } else {
          // The error is unexpected. Authentication details might be 
          // incorrect. Note: In production code, use a C++ exception instead
          throw std::string("could not login (" + response + ")");
      }
   }

	 // Extract the session ID, session nonce and date and date/time 
   // of session expiry (ValidThru) and combine them into a session key.
   auto sessionKey = zeuz_hash(response_json["Data"]["SessionNonce"].asString() + pwhash);

   // Return the session ID, session key and ValidThru as a data struct
   return LoginData{
       .SessionId = response_json["Data"]["SessionID"].asString(),
       .SessionKey = sessionKey,
       .ValidThru = response_json["Data"]["ValidThru"].asInt64(),
   };
}

Example 4: Generate a sign-hash, send a JSON packet to the auth_check API endpoint, and parse the return value to see if the logged in session is valid or invalid. If it’s valid, send a new cURL request to a zeuz API endpoint (C++)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
#include <jsoncpp/json/writer.h>
#include <jsoncpp/json/reader.h>

// Check if the session timestamp is still valid
bool ZeuzApi::check_session(long now, LoginData login_data) {
    // Check that the expiry time is at least 60 seconds away
    if (now > (login_data.ValidThru - (60 * 1000000))) {
        return false;
    }
    return true;
}

// Format according to doc.zeuz.io/reference#auth_check
// Modify as needed for other zeuz API endpoints
std::string format_to_json_payload(std::string request_id, std::string session, std::string sign_hash, long time, std::string session_id) {
   Json::Value data;
   Json::FastWriter writer;
   data["ReqID"] = request_id;
   data["Session"] = session;
   data["SignHash"] = sign_hash;
   data["Time"] = Json::Int64(time);
   data["Data"] = session_id;
   return writer.write(data);
}

// Call the auth_check API endpoint to verify whether
// the logged in session is still valid
bool ZeuzApi::auth_check(LoginData login_data) {
   auto now = zeuz_now();
   if (!check_session(now, login_data)) {
       // If the session has expired, it is no longer valid
       return false;
   }
   auto request_id = generate_nonce(10);

   // Generate a sign-hash
   auto sign_hash = zeuz_hash(std::to_string(now) + request_id + login_data.SessionKey);

	 // Attach the sign-hash and other data required by the zeuz 
   // API endpoint
   auto data = format_to_json_payload(request_id, login_data.SessionId, sign_hash, now, login_data.SessionId);

   // See the post_request function in Example 2
   // Modify the CURLOPT_URL and other properties according to the
   // requirements of the API endpoint you want to call
   response = post_request("https://zcp.zeuz.io/api/v1/auth_check", data);

   // Parse the JSON response and extract the values.
   Json::Reader reader;
   Json::Value response_json;
   if (!reader.parse(response, response_json)) {
      // Note: In production code, use a C++ exception instead
      throw std::string("could not parse response data ( " + response + ")");
   }
 
   // Check whether the error field is set
   auto error = response_json["Error"].asString();
   if (!error.empty()) {
      if (error.rfind("invalid_session", 0) == 0 ||
          error.rfind("session_expired", 0) == 0) {
          // If the session has expired, perform another login
          return false;
      } else {
          throw std::string("could not check session (" + response + ")");
      }
   }

   // If no error is received, the session is valid
   return true;
}


2021-may-12 Page added with editorial review.


Last edited on: September 23, 2021 (a8f4a430)