POST to /token for refresh token was not working. Here's what I did to fix it

Hello community,
I ran into an issue when trying to utilize the refresh token API from our app. I was following the instructions posted over here at Getting Started with OAuth2 - Keap Developer Portal under Refresh Request and reading through various threads here in the community about similar issues other developers were having.

Despite what I tried I kept getting InvalidClientIdentifier errors in Postman and from my app. But I knew that there was a UI in the API Access dashboard for my sandbox app that would let me input a Client ID and Client Secret to obtain a refresh token and access token. I used the Network Tab to see what headers and payload were being sent along with the submission. What I found was that although the documentation states to concatenate the Client ID and Client Secret with a “:” in a Base64 encoded format, the Authorization Basic header was formatted to NOT have the “:” between the two values.

API Access Dashboard UI

Console Demo
Console Demo

Network Tab

As you can see here By running the formula suggested in the documentation, I get the values concatenated with a “=:” set of characters. In the network tab, I noticed that in place of those characters was a “6”.

When I made this change in my resulted base 64 encoded string in Postman, it instantly started working and returning a 200 response with the correct response payload.

I’m in the process of creating another sandbox account so I can verify if this is ALWAYS replaced with a “6” or if it is some other random character.

base64_id = Base64.encode64(ENV['INFUSIONSOFT_KEY']).gsub(/\r?\n|\r/, '') # need to strip out \n
base64_secret = Base64.encode64(ENV['INFUSIONSOFT_SECRET']).gsub(/\r?\n|\r/, '')
"Basic #{base64_id}:#{base64_secret}".gsub("=:", '6') # replace the weird =: characters with a 6

I’m a bit confused, why are you encoding the key and secret separately? I’m not well versed in Ruby (which it appears you are using) but something like the following should work fine as the Authorization header.

credentials = Base64.encode64(ENV['INFUSIONSOFT_KEY'] + ":" +ENV['INFUSIONSOFT_SECRET'])
"Basic #{credentials)"

I found that when I use the formula

# Random Hex values used here
=> "NjIxNGJhMjc3Mjc0YWQ0ODE1ODhkOTE3ZWY2OGU3YzA6Njk2MTJlZGVhMzlm\n" + "YzcyNQ==\n"

It produces this set of values with \n" + " which is completely different than if you encode the keys separately and then add in the “:” (You don’t want to base encode the “:”)

# again, these are fictitious values
=> "Njk2MTJlZGVhMzlmYzcyNQ==\n"

It seems like the problem you are having is an artifact of Ruby’s helper functions. Concatenating them seperataely will not improve that: Module: Base64 (Ruby 2.5.3) . I’m not sure why the implementers of that function decided to suffix it with a newline, but that’s the source.

require "base64"

enc   = Base64.encode64('Send reinforcements')
                    # -> "U2VuZCByZWluZm9yY2VtZW50cw==\n"

I assure you that you do indeed want to encode the colon. It is part of the specification for the Basic Authentication scheme. RFC 7617 - The 'Basic' HTTP Authentication Scheme

   The user's name is "test", and the password is the string "123"
   followed by the Unicode character U+00A3 (POUND SIGN).  Using the
   character encoding scheme UTF-8, the user-pass becomes:

      't' 'e' 's' 't' ':' '1' '2' '3' pound
      74  65  73  74  3A  31  32  33  C2  A3

   Encoding this octet sequence in Base64 ([RFC4648], Section 4) yields:


 Thus, the Authorization header field would be:

      Authorization: Basic dGVzdDoxMjPCow==

Thanks Tom, that makes sense. Thank you for pointing out the suffixing on the Base64 module. I didn’t think to dig that deep. It looks like if I strip out the newline characters at the same time as the Base64 encoding, it results in the same thing as if I were to encode them separately, but without the “=:” in the middle to be taken care of in a regex replace.

Thank you for pointing this out. It saves me from having to work around a brittle regex.

Here’s the diff for others reading along:

# proper unified encoding
Base64.encode64("6214ba277274ad481588d917ef68e7c0:69612edea39fc725").gsub(/\r?\n|\r/, '')
"Basic NjIxNGJhMjc3Mjc0YWQ0ODE1ODhkOTE3ZWY2OGU3YzA6Njk2MTJlZGVhMzlmYzcyNQ=="
=> "Basic NjIxNGJhMjc3Mjc0YWQ0ODE1ODhkOTE3ZWY2OGU3YzA6Njk2MTJlZGVhMzlmYzcyNQ=="

# vs separate encoding
Base64.encode64("6214ba277274ad481588d917ef68e7c0").gsub(/\r?\n|\r/, '')
Base64.encode64("69612edea39fc725").gsub(/\r?\n|\r/, '')
=> "Njk2MTJlZGVhMzlmYzcyNQ=="
"Basic NjIxNGJhMjc3Mjc0YWQ0ODE1ODhkOTE3ZWY2OGU3YzA=:Njk2MTJlZGVhMzlmYzcyNQ=="
=> "Basic NjIxNGJhMjc3Mjc0YWQ0ODE1ODhkOTE3ZWY2OGU3YzA=:Njk2MTJlZGVhMzlmYzcyNQ=="
# then I would have to regex out that `=:` (ew)