Can't get OAuth2 access token with grant type client credentials

Hi,

I’m trying to get an OAuth2 access token using the grant type client credentials.

I’ve put the details into environment variables and I’m able to get the token with a humble curl request:

$ curl -X POST -u "$CLIENT_ID:$CLIENT_SECRET" -d "grant_type=client_credentials" https://$HOST$TOKEN_URI_SUFFIX     
{"access_token":"S-OW6C7WwIR0Kl_d9PMIjg","token_type":"bearer","expires_in":3600}

However, if I try the same with Crystal it fails.

Code:

require "oauth2"

host = ENV["HOST"]
client_id = ENV["CLIENT_ID"]
client_secret = ENV["CLIENT_SECRET"]
token_uri_suffix = ENV["TOKEN_URI_SUFFIX"]

oauth2_client = OAuth2::Client.new(
  host: host,
  client_id: client_id,
  client_secret: client_secret,
  token_uri: token_uri_suffix
)

begin
  access_token = oauth2_client.get_access_token_using_client_credentials
rescue ex : OAuth2::Error
  puts ex.class
  puts ex.message
end

Result:

$ crystal oauth2-test.cr                            [14:16:54]
OAuth2::Error
<!DOCTYPE html>
...
<head>
	<title>Unauthorized</title>
	
	<meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
</head>
...

I would be interested to know if anyone is able to use the OAuth2::Client#get_access_token_using_client_credentials method succesfully (and if so, please let me know what I’m doing wrong) or reproduce this issue?

Does TOKEN_URI_SUFFIX contain any query parameters? I could imagine the current implementation loosing or incorrectly encoding anything but the path component. Does passing the fully composed URI as token_uri work for you?

No the TOKEN_URI_SUFFIX does not contain any query parameters.

I get the same result if I fully compose the URI (token_uri: "https://#{host}#{token_uri_suffix}")

Ah, I see the difference now. OAuth2 posts the client_idand client_secret as a application/x-www-form-urlencoded body to the endpoint instead of using them for HTTP basic authentication.

The spec describes either behavior with a MAY https://tools.ietf.org/html/rfc6749#section-2.3.1 but it has a SHOULD on preferring the HTTP basic way, so we probably should default to that while still giving an option to include it in the request body for servers that don’t support HTTP basic authentication. I would say a pull request for this is welcome :)

I’m afraid for now your best bet is reimplementing OAuth2::Client or monkey-patching https://github.com/crystal-lang/crystal/blob/master/src/oauth2/client.cr#L147-L166

OK - just to confirm that I got it working with a monkey patch.

I will create a PR for changes to OAuth2 (hopefully in the next week or so).

1 Like

@jhass having re - read the spec again, it says:

Including the client credentials in the request-body using the two parameters is NOT RECOMMENDED and SHOULD be limited to clients unable to directly utilize the HTTP Basic authentication scheme (or other password-based HTTP authentication schemes).

(my italics)

Since it says clients (not servers), and since a Crystal client will be capable of HTTP Basic authentication, my reading is that the Crystal OAuth2 library should always use HTTP Basic.

Do you agree?

This would also have the advantage that there would be no need to change the API of the OAuth2::Client.

I think it would be acceptable, but given the landscape of OAuth2 I’ve seen in the wild I would prefer still having the option I think. It should be possible to make it an optional named parameter, so no real API change unless your server is buggy and requires the current implementation :)

OK, I’ll do it with an option then :slight_smile:

1 Like

@jhass https://github.com/crystal-lang/crystal/pull/9127

1 Like