Authorization troubles on Advanced Trading API

I’m sure I’m just missing something, but this makes me crazy.

Following the code examples in the API documentation, I continue to get “Unauthorized” as a response. I’m sure I’m missing something simple but can’t find it.

The code below is what I put together to test authorization before I build out any further. Have no problem with the Pro API, but I think I’ve looked at this code too much to see the error.

Key and secret removed for security but are the ones created on Coinbase, not Pro

from requests.auth import AuthBase
import hmac, hashlib, time, base64
import requests


api_key = 'xxxxxxxxxxxxx'
api_secret = 'xxxxxxxxxxxxxxxxxxxxxxxxxxx'
test_url = 'api/v3/brokerage/accounts'
base_url = 'https://coinbase.com/'

class CBAuth(AuthBase):

    def __init__(self, api_secret,api_key, api_url):
        # setup any auth-related data here
        self.api_secret = api_secret
        self.api_key = api_key


    def __call__(self, request):
        timestamp = str(int(time.time()))
        message = timestamp + request.method + test_url
        signature =  hmac.new(api_secret.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()

        request.headers.update({
            'CB-ACCESS-SIGN': signature.hex(),
            'CB-ACCESS-TIMESTAMP': timestamp,
            'CB-ACCESS-KEY': self.api_key,
            'accept': "application/json"

        })
        print(message)
        print(request.headers)
        return request

auth = CBAuth(api_key,api_secret,test_url)
url = base_url + test_url
response = requests.get(url,auth=auth )
print(response)
print(response.text)

The code returns (keys and signature blanked for security):

{'User-Agent': 'python-requests/2.28.1', 'Accept-Encoding': 'gzip, deflate', 'accept': 'application/json', 'Connection': 'keep-alive', 'CB-ACCESS-SIGN': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', 'CB-ACCESS-TIMESTAMP': '1668701715', 'CB-ACCESS-KEY': 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'}
<Response [401]>
Unauthorized

What am I missing?

1 Like

Is signature in hex?

From documentation:

  1. Get the hexadecimal string representation of the sha256 HMAC object and pass that in as the CB-ACCESS-SIGN header.

Is that not happening here:

            'CB-ACCESS-SIGN': signature.hex(),
            'CB-ACCESS-TIMESTAMP': timestamp,
            'CB-ACCESS-KEY': self.api_key,
            'accept': "application/json"

Oh, sorry… I missed that part.

Otherwise check if request.method is uppercase. Or I guess your test_url is wrong… Request path should start with /.

Hmm… tried both of those. Changed request.method to request.method.upper() and changed the request path (test_url) to start with /.

Same result. This is vexing.

Checking the message before encoding, it looks like this:

1668704747GET/api/v3/brokerage/accounts

Does that look right?

Yes. Try https://api.coinbase.com as base_url!?

Same result. I am sure this is something I’m missing. But can’t see it.

Found it. For some reason, the api_secret was being applied to api_key. Weird. Now fixed - Thanks!

Hi,

I’m having trouble with the authorization subscribing to a websocket channel. I’m using python and generating the signature with the same sample code from the REST API and just replacing the message with what is described in websocket section of the documentation.

signature = hmac.new(secretKey.encode(‘utf-8’), message.encode(‘utf-8’), digestmod=hashlib.sha256).digest()

Where:
message = concatenating unix timestamp + channel + product_ids (comma separated)

Connection Code:

    ws = create_connection('wss://advanced-trade-ws.coinbase.com')
    ws.send(jjson.dumps({
        'type':'subscribe',
        'product_ids':coinPairs,
        'channel':'ticker_batch',
        'api_key':self.Key,
        'timestamp':timestamp,
        'signature':signature,
        }))

What message are you getting? And are you sending all the headers?

Thanks for the follow up, I figured it out. I had the order of the key and message swapped in the where I was defining the signature, turned out to be the problem.

Hi @channa48! Thanks for letting us know this all worked out. Have a great day!

2 Likes

hey @lsoderman, … how did you fix it? did you change anything in your code? thanks

1 Like

Yup. I was calling for self.api_key (which was appropriately assigned…) when I was building the auth. Somehow, it was passing the api_secret. My fix was to just use the base api_key rather than the class definition. Fixed it.

2 Likes

Hey @channa48
I am facing the same issue.
did you set digest.hex() or is it base64?
the snippet you posted here it is a byte and when i tried that i get error of type bytes is not JSON serializable

Below is the my snippet in case that helps

load_dotenv()

API_KEY = os.getenv('CB_API_KEY')
SECRET = os.getenv('CB_API_SECRET')

def sign_message(message):
    digest = hmac.new(SECRET.encode('utf-8'), message.encode('utf-8'), digestmod=hashlib.sha256).digest()
    print(f"digest: {digest}")
    # return base64.b64encode(digest).decode()
    return digest.hex()

subscribe_msg = '''
{
    "type": "subscribe",
    "product_ids": [
        "ETH-USD"
    ],
    "channel": "ticker_batch",
    "api_key": " ",
    "timestamp": " ",
    "signature": " "
}
'''
subs = json.loads(subscribe_msg)
subs["api_key"] = API_KEY
subs["timestamp"] = int(time.time())
subs["signature"] = sign_message(str(subs["timestamp"])+"ticker_batch"+"ETH_USD")

ws = websocket.create_connection("wss://advanced-trade-ws.coinbase.com")
ws.send(json.dumps(subs))

Thanks in advance for your help

1 Like

Did you try ETH-USD instead ETH_USD in signature message?

1 Like

yeah, tried that as well and same auth error

1 Like

Might be me not reading it right, but I don’t see the request path in the signature encoding. Per the docs:

Concatenate the values of the following parameters with the + operator: these query parameters: timestamp + method + requestPath + body.

  • timestamp is the same as the CB-ACCESS-TIMESTAMP header (+/-30 seconds)
  • method should be UPPER CASE
  • requestPath is the full path (minus the base URL and query parameters), for example:
    • /api/v3/brokerage/orders/historical/fills
    • /api/v3/brokerage/products/BTC-USD/ticker
  • body is the request body string – it is omitted if there is no request body (typically for GET requests)
1 Like

hey @lsoderman the path is needed for rest api.
my struggle us with the websockets and the channel name is included in there

1 Like