sha256 HMAC API call

Hi,

I attempting to learn API calls and am not having much success, particularly with authorization and authentication procedures. For instance, the Coinbase API documentation mentions:

All REST requests must contain the following headers:

  • CB-ACCESS-KEY The api key as a string.
  • CB-ACCESS-SIGN The base64-encoded signature (see Signing a Message).
  • CB-ACCESS-TIMESTAMP A timestamp for your request.
  • CB-ACCESS-PASSPHRASE The passphrase you specified when creating the API key.

All request bodies should have content type application/json and be valid JSON.

The CB-ACCESS-SIGN header is generated by creating a sha256 HMAC using the base64-decoded secret key on the prehash string timestamp + method + requestPath + body (where + represents string concatenation) and base64-encode the output. The timestamp value is the same as the CB-ACCESS-TIMESTAMP header.

The body is the request body string or omitted if there is no request body (typically for GET requests).

The method should be UPPER CASE.

Remember to first base64-decode the alphanumeric secret string (resulting in 64 bytes) before using it as the key for HMAC. Also, base64-encode the digest output before sending in the header.

Now how do I translate this into an actual API call? This is the method they suggest:

var crypto = require('crypto');

var cb_access_timestamp = Date.now() / 1000; // in ms
var cb_access_passphrase = '...';
var secret = 'PYPd1Hv4J6/7x...';
var requestPath = '/orders';
var body = JSON.stringify({
    price: '1.0',
    size: '1.0',
    side: 'buy',
    product_id: 'BTC-USD'
});
var method = 'POST';

// create the prehash string by concatenating required parts
var message = cb_access_timestamp + method + requestPath + body;

// decode the base64 secret
var key = Buffer(secret, 'base64');

// create a sha256 hmac with the secret
var hmac = crypto.createHmac('sha256', key);

// sign the require message with the hmac
// and finally base64 encode the result
var cb_access_sign = hmac.update(message).digest('base64');

The API documentation actually has an R request example, but it still doesn't show how to make the full call it seems.

library(httr)

url <- "https://api.exchange.coinbase.com/accounts"

response <- VERB("GET", url, 
             add_headers(cb_access-key = 'key', 
             cb_access-passphrase = 'phrase', 
             cb_access-sign = 'sign', 
             cb_access-timestamp = 'timestamp'), 
content_type("application/octet-stream"), accept("application/json"))

content(response, "text")

I think it comes down to making that CB-ACCESS-SIGN signature and then the four headers need to be added?

I've got working code, thanks to the rgdax package. The package isn't currently working but I can run the code manually and get it to be successful. Now what I'd like to do is update the code into httr2 terminology.

api.url <- "https://api.pro.coinbase.com"
req.url <- "/accounts/"
secret <- "asdf"
api.key <- "1234"
passphrase <- "123"

url <- paste0(api.url, req.url) #"https://api.pro.coinbase.com/accounts/"
timestamp <- format(as.numeric(Sys.time()), digits = 13) # create nonce 
key <- RCurl::base64Decode(secret, mode = "raw") # encode api secret 
method <- "GET" 
what <- paste0(timestamp, method, req.url) # get method 

#create encoded signature----
sign <- RCurl::base64Encode(digest::hmac(key, what, algo = "sha256", raw = TRUE)) # hash 

httpheader <- c(
    'CB-ACCESS-KEY' = api.key,
    'CB-ACCESS-SIGN' = sign,
    'CB-ACCESS-TIMESTAMP' = timestamp,
    'CB-ACCESS-PASSPHRASE' = passphrase,
    'Content-Type' = 'application/json'
  ) 

# Generating GET results
response <- httr::content(httr::GET(url, httr::add_headers(httpheader))) 

#transform----
response <- plyr::ldply(response, data.frame) 

response

Oddly enough I get an error with httr2 despite sending what I believe to be the same call.

# Initial Request Inputs
base_url <- "https://api.pro.coinbase.com"
req_url <- "accounts/"
method <- "GET"

# API inputs
api_key <- "1234"
passphrase <- "123"

# Build Signature
secret <- "asdf"
timestamp <- format(as.numeric(Sys.time()), digits = 13) # create nonce
key <- RCurl::base64Decode(secret, mode = "raw") # encode api secret
what <- paste0(timestamp, method, req_url) # get method
sign <- RCurl::base64Encode(digest::hmac(key, what, algo = "sha256", raw = TRUE)) 


# Create Request
request <- request(base_url) %>% 
  req_url_path_append(req_url) %>% 
  req_headers('Accept' = "application/json, text/xml, application/xml, */*",
              'CB-ACCESS-KEY' = api_key,
              'CB-ACCESS-SIGN' = sign,
              'CB-ACCESS-TIMESTAMP' = timestamp,
              'CB-ACCESS-PASSPHRASE' = passphrase,
              'Content-Type' = 'application/json')

The request object looks like this:

<httr2_request>
GET https://api.pro.coinbase.com/accounts/
Headers:

  • Accept: 'application/json, text/xml, application/xml, /'
  • CB-ACCESS-KEY: '1234'
  • CB-ACCESS-SIGN: 'asdf'
  • CB-ACCESS-TIMESTAMP: '1637865183.944'
  • CB-ACCESS-PASSPHRASE: '123'
  • Content-Type: 'application/json'
    Body: empty

Which perfectly matches the other request, but when I perform it:

# Perform Request
resp <- req_perform(request, verbosity = 3)

It returns

{"message":"invalid signature"}