Subscription Authentication Error

Hello, I’ve been away for a while and you go and get a whole new server on me. Anyway I’m back on my project but first I need to migrate my coinbaseClient class.

The migration docs are very straightforward and I believe I followed them correctly. However I’m getting authentication failures with my status and ticker subscriptions and hit a brick wall.

Subscription message as follows (xxx sensitives removed xxx):
{“type”:“subscribe”,“product_ids”:[“BTC-USD”],“channel”:“status”,“api_key”:“xxx”,“timestamp”:“1673105226”,“signature”:“xxx”}

Signature generated with the following prehash example:
1673105228statusBTC-USD

and code (key refers to the secret key and not the api key called for in the json string):

private String hash(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException {
Mac sha256_HMAC = Mac.getInstance(“HmacSHA256”);
byte secretDecoded = Base64.getDecoder().decode(key);
SecretKeySpec secret_key = new SecretKeySpec(secretDecoded, “HmacSHA256”);
sha256_HMAC.init(secret_key);
return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(prehash.getBytes()));
}

According to the documentation this should work just fine and did so on the Coinbase Pro servers. Any insight would be greatly appreciated.

Hello @cleggink! Thank you for taking an interest in trying out Coinbase APIs. For the details regarding your concern, we will check on this for you with our team. We will get back to you once we have more information. Keep in touch!

Is there a time limit after api key creation before you can use it? I’ve tried every iteration of keys and prehashes and prehash delimiters, etc… rewrote the hash function a couple times, even asked a chatGPT bot out of pure fustration to re-write the js example from the docs to java. It gave me basically the same algorithm I wrote anyway.

Hello @cleggink! With regard to your concern if there’s a time limit after API key creation before you can use it, if you’ve just created a new API key on a newly confirmed device, your newly created API key will be temporarily disabled. This is due to security reasons of Coinbase which requires a 48-hour wait period on API key activation on new devices. You should receive an email or SMS from Coinbase stating that the API key you have created will be temporarily disabled as this is part of our layered security features to protect customers’ digital assets. Otherwise, if you’ve created an API Key on a device that has already been confirmed for longer than 48 hours, then it should be active as soon as it has been created and you may now already use it right away without a wait period.

On the other hand, regarding your previous concern about the Authentication error, we’ve already escalated this with our team and we’re still waiting for their guidance.

While my account is a year or two old now this is the first time I’ve setup the advanced trade api so im sure that the 48 hr time limit is in effect. Thanks for the information.

@Faker this is the date of api key creation:
Created at: January 06, 2023 17:08
Last updated at: January 06, 2023 17:08

and as of 1/9/23 7:45am, over the 48hr probation period, still getting authentication errors.

How do you confirm a device when using api keys?

Hi @cleggink! Thank you so much for your patience. We are sorry that you are still experiencing the Subscription Authentication Error with the Advanced Trade API. Please know that our internal teams are still hard at work to resolve your query. As for your question “How do you confirm a device when using api keys?”, we want to inform you that device confirmation/authorization is done through clicking “Yes, trust” when prompted by the “Trust this Device for 30 days?” screen upon logging in to the new device. You will then need to tap on the “I Authorize This Computer” in the email Coinbase sent to finish the steps through Coinbase.com. If you didn’t receive the email, check your Spam or Trash folders. You might need to add (no-reply@coinbase.com) to your email contacts to prevent their messages from being flagged as spam.

Device confirmation/authorization is not done via API keys, disabled API key is a result of unconfirmed devices of which Coinbase requires a 48-hour wait period on API key activation as part of their layered security features to protect customer digital assets. We understand that you might think that your API key being disabled may be the reason for your Authentication error, our team is still looking into it and we will provide feedback as soon as we can. Keep posted!

1 Like

Ok so just to clarify if I’ve logged into the Coinbase website from the system that i am running my app from then the device was already confirmed by the website? Is that correct?

Hello @cleggink, yes you are correct. If you have successfully logged into the Coinbase website from the system you are running your app from, then your device has already been authorized/confirmed.

I was hoping that would have been my problem. It has to be my hash function then i just dont see the error thou

// ENCRYPTION
private String subscriptionAuth(String pMessage, String[] productIDList, String channel) { // SUBSCRIPTION SIGNATURE
																							// BUIDER
	String jsonMessageFinal = pMessage;

	try {
		String timeStamp = Long.toString(Instant.now().getEpochSecond());
		jsonMessageFinal = String.join("", jsonMessageFinal, ",\"api_key\":\"", coinbaseAPIKey,
				"\",\"timestamp\":\"", timeStamp, "\",\"signature\":\"",
				this.hashHex(String.join("", timeStamp, channel, String.join(",", productIDList)),
						coinbaseSecretKey), "\"");

	} catch (Exception e) {
		this.logger("EXCEPTION", e.toString(), e);
	}
	return jsonMessageFinal;
}

private String hashHex(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {

	char[] encodedString = hash(prehash, key);
	StringBuffer hexBuff = new StringBuffer();
	
	for(int i = 0; i < encodedString.length; i++) {
		hexBuff.append(Integer.toHexString(encodedString[i]));
	}
	return hexBuff.toString();
}

private char[] hash(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {
	
	System.out.println(prehash); // TODO: REMOVE THIS LINE
	Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
	sha256_HMAC.init(new SecretKeySpec(Base64.getDecoder().decode(key), "HmacSHA256"));
	return Base64.getEncoder().encodeToString(sha256_HMAC.doFinal(prehash.getBytes("UTF-8"))).toCharArray();
}

Is your json message correct? For example, I don’t see opening { and closing } brackets…

Last attempt at the advanced trade api:

{“type”:“subscribe”,“product_ids”:[“BTC-USD”],“channel”:“ticker”,“api_key”:“REDACTED”,“timestamp”:“1673640716”,“signature”:“694a5a6b69656a4e4c5a585838477368324c63316232796a4450484c657231374762504e6d6c4b374b33303d”}

I just keep getting authentication errors which is why I think it’s in the signature

@muktupavels

You don’t need to decode secret key, maybe that is the problem?

This is the same function I used for the Coinbase Pro servers. and the sha256_HMAC.init() takes a byte the decoding function I assumed was how this was accomplished. I double checked by converting the sample javascript code in the docs, the ai came up with a less streamlined but still the same version as what I have here. If I’m wrong about this then please elaborate on how to fix this error. Thanks @muktupavels .

Sending the key as a regular byte :
sha256_HMAC.init(new SecretKeySpec(key.getBytes(), “HmacSHA256”));
didn’t change the result.

You can forget about functions you used with Coinbase Pro. Advanced Trade is not same and your code requires changes! In Advanced Trade secret key is not base64 encoded so you don’t need to decode it. Did you check WebSocket Overview | Coinbase Cloud?

As for your edit - you also don’t need to base64 encode signature! Read/check documentation!

Yes I’ve seen the docs, but that is javascript and im using java and those functions are what i used with the pro servers and they worked just fine. I’ll try and convert the js to java again and see if it changes anything.

So I attempted the changes you suggested @muktupavels , nothing changes the authentication error. Perhaps you have a signature function for java that works already and we can stop beating around this bush.

Current code:

// ENCRYPTION
private String subscriptionAuth(String pMessage, String[] productIDList, String channel) { // SUBSCRIPTION SIGNATURE
																							// BUIDER
	String jsonMessageFinal = pMessage;

	try {
		String timeStamp = Long.toString(Instant.now().getEpochSecond());
		jsonMessageFinal = String.join("", jsonMessageFinal, ",\"api_key\":\"", coinbaseAPIKey,
				"\",\"timestamp\":\"", timeStamp, "\",\"signature\":\"",
				this.hashHex(String.join("", timeStamp, channel, String.join(",", productIDList)),
						coinbaseSecretKey), "\"");

	} catch (Exception e) {
		this.logger("EXCEPTION", e.toString(), e);
	}
	return jsonMessageFinal;
}

private String signMessage(String timestamp, String method, String path, String body) // ENCRYPTED HASH SECURITY
																						// SIGNATURE BUILDER
		throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException {

	String prehash = timestamp + method.toUpperCase() + path + body;
	return hashHex(prehash, this.coinbaseSecretKey);
}

private String hashHex(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {

	char[] encodedString = hash(prehash, key).toString().toCharArray();
	StringBuffer hexBuff = new StringBuffer();
	
	for(int i = 0; i < encodedString.length; i++) {
		hexBuff.append(Integer.toHexString(encodedString[i]));
	}
	return hexBuff.toString();
}

private byte[] hash(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {
	
	Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
	sha256_HMAC.init(new SecretKeySpec(key.getBytes("UTF-8"), sha256_HMAC.getAlgorithm()));
	return sha256_HMAC.doFinal(prehash.getBytes("UTF-8"));
}

Problem solved, the original signature function I converted from JS wasn’t converting to hexadecimal correctly. Here is a working java method:

// ENCRYPTION
private String subscriptionAuth(String pMessage, String productIDList, String channel) { // SUBSCRIPTION SIGNATURE
// BUIDER
String jsonMessageFinal = pMessage;

	try {
		String timeStamp = Long.toString(Instant.now().getEpochSecond());
		jsonMessageFinal = String.join("", jsonMessageFinal, ",\"api_key\":\"", coinbaseAPIKey,
				"\",\"timestamp\":\"", timeStamp, "\",\"signature\":\"",
				this.hash(String.join("", timeStamp, channel, String.join(",", productIDList)),
						coinbaseSecretKey), "\"");

	} catch (Exception e) {
		this.logger("EXCEPTION", e.toString(), e);
	}
	return jsonMessageFinal;
}

private String signMessage(String timestamp, String method, String path, String body) // ENCRYPTED HASH SECURITY
																						// SIGNATURE BUILDER
		throws InvalidKeyException, NoSuchAlgorithmException, IllegalStateException, UnsupportedEncodingException {

	String prehash = timestamp + method.toUpperCase() + path + body;
	return hash(prehash, this.coinbaseSecretKey);
}

private String hash(String prehash, String key) throws NoSuchAlgorithmException, InvalidKeyException, IllegalStateException, UnsupportedEncodingException {

	Mac sha256_HMAC = Mac.getInstance("HmacSHA256");
	sha256_HMAC.init(new SecretKeySpec(key.getBytes("UTF-8"), sha256_HMAC.getAlgorithm()));
	byte[] hash = sha256_HMAC.doFinal(prehash.getBytes());
	StringBuilder hexString = new StringBuilder();
	
	for (byte aHash : hash) {
		String hex = Integer.toHexString(0xff & aHash);
		
		if (hex.length() == 1) {
			hexString.append('0');
		}
		hexString.append(hex);
	}
	return hexString.toString();
}

Hello @cleggink, I’m having the same issue using the Advanced Trade API’s WebSocket feed and am sending essentially the same subscription message as yours. The response message I receive from coinbase is

{“type”:“error”,“message”:“authentication failure”}

Certainly not very informative. It should be relatively straightforward for coinbase to examine their logs and code to determine the cause of an authentication failure. The subscription message should be able to be found in the logs based on the api_key.

I am i including the c++ code I’m using to generate the subscription message along with the output of one of the subscription messages. The c++ CryptoPP and jsoncpp APIs are being used.

namespace coinbase {
static Json::Value GetSubscribeMsg()
{
Json::Value root;

    std::string channel_name = "level2";
    std::vector<std::string> crypto_ids = {"BTC-USD"};
    
    root["type"] = Json::Value("subscribe");
    
    Json::Value product_ids(Json::ValueType::arrayValue);
    size_t index = 0;    
    for (auto i: crypto_ids)
    {             
        product_ids.insert(index++, i);
    }    
    root["product_ids"] = product_ids;

    root["channel"] = "level2";
        
    root["api_key"] = "xxx";
    
    // The new way
    using clock = std::chrono::system_clock;    
    std::time_t now = clock::to_time_t(clock::now());
    root["timestamp"] = now;

    // The signature should be created by first concatenating
    // comma-seprating the UNIX timestamp and channel name and
    // then comma-seprating the list of product Ids. Then hashing
    // the phrase using Hmac256 with the secret key.
    //---------------------------------------------------------
    std::string signature = std::to_string(now) + channel_name;
    if (crypto_ids.size() == 1)
        signature += crypto_ids.front();
    else
    {        
        for (auto i = crypto_ids.begin(); i != crypto_ids.end() - 1; i++)
        {
            signature.append(*i);
            signature.append(",");
        }
        signature += crypto_ids.back();        
    }

    std::cout << "signature: " << signature << std::endl;

    std::string secret = "yyy";

    // The Exchange/Pro API specifies to 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. The Advanced
    // Trade API does not require this decoding and encoding.
    // Thus, secret_base64_decoded is not used here.
    std::string secret_base64_decoded;
    CryptoPP::StringSource ss1(secret, true,
                               new CryptoPP::Base64Decoder(
                                   new CryptoPP::StringSink(secret_base64_decoded)));
    //-------------------------------------------------------------------------------

    std::cout << "secret: " << secret << std::endl;
        
    std::string digest;

    try
    {
        CryptoPP::HMAC< CryptoPP::SHA256 > hmac((const unsigned char*)secret.c_str(),  secret.size());
        
        CryptoPP::StringSource ss2(signature, true,
                                   new CryptoPP::HashFilter(hmac,
                                       new CryptoPP::StringSink(digest)));
    }
    catch(const CryptoPP::Exception& e)
    {
        std::cerr << e.what() << std::endl;
        exit(1);
    }

    // Hex encode the digest output. Use lower case hex characters
    // to match toString of crypto-js HmacSHA256 used in the
    // example in the Advanced Trade API docs
    //-------------------------------------------------------------
    std::string hmac_hex_encoded;
    CryptoPP::StringSource ss3(digest, true,
                               new CryptoPP::HexEncoder(
                                   new CryptoPP::StringSink(hmac_hex_encoded)));

    std::transform(hmac_hex_encoded.begin(), hmac_hex_encoded.end(), hmac_hex_encoded.begin(),
                   [](unsigned char c){ return std::tolower(c); });
                       
    root["signature"] = Json::Value(hmac_hex_encoded);

    return root;
}

} // namespace coinbase