New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Oauth2 Client Credentials Grant not possible #384

Closed
cderv opened this Issue Jul 14, 2016 · 3 comments

Comments

Projects
None yet
3 participants
@cderv
Contributor

cderv commented Jul 14, 2016

Hi,

I tried using httr with Oauth2 authorization on some API of my company. This API is working with the Client Credentials Grant as described here. It is one of the 4 grant type for Oauth2.

In this grant type, no authorization request is needed, only access token requests are made.
Access request are made with client id and client secret, in an authorization header. Nothing is passed as request parameters (as there is no authorization code). Request should looks like this :

`POST /token HTTP/1.1
     Host: server.example.com
     Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW
     Content-Type: application/x-www-form-urlencoded

     grant_type=client_credentials

First, I tried to use oauth2.0_token but it did not work. It took me a while to understand why there was a problem. It seems that oauth2.0_token works for a certain type of grant where authorization is needed. init_oauth2.0 calls in its workflow oauth_exchanger or oauth_listener on a authorize url. This step of authentification request is not optional, which is not compatible with the client credendial grant.

The other issue is related to the fix of #288 with client credential in the header. Option use_basic_auth works but there is still a body passed in the request:

if (isTRUE(use_basic_auth)) {
      req <- POST(
        endpoint$access,
        encode = "form",
        body = req_params,
        authenticate(app$key, app$secret, type = "basic")
      )
    }

req_params contains some parameters not needed in the client credential grant, in particular their is no code to pass.

Knowing all that, in order to use httr Oauth dance, I extended the Token2.0 ref class to implement a new initialize method that calls a modified init_oauth2.0 function in which I just commented the authorize request part, and call POST with no code param in the body. It's a quick fix I did today but it works.

So,

  • Did I missed something and it should have worked in the first place ?
  • Is it a good idea to create a new extended class for that ?
  • Should it be rather a fix in the init_oauth2.0 function as an option to get an access token without any authorization request and code beforehand ? like use_basic_auth arg in #288 which offer a choice in the method.

I just experiment it today but I'll be glad to share my works and findings if it is any interest.

@jennybc

This comment has been minimized.

Member

jennybc commented Dec 16, 2016

In this grant type, no authorization request is needed, only access token requests are made... First, I tried to use oauth2.0_token but it did not work... This step of authentification request is not optional, which is not compatible with the client credendial grant.

I believe the Yelp Fusion API is another, public example of such an API.

4.4. Client Credentials Grant: https://tools.ietf.org/html/rfc6749#section-4.4

Usage from first principles:

library(httr)

res <- POST("https://api.yelp.com/oauth2/token",
            body = list(grant_type = "client_credentials",
                        client_id = Sys.getenv("YELP_ID"),
                        client_secret = Sys.getenv("YELP_SECRET")))
token <- content(res)$access_token
(url <-
    modify_url("https://api.yelp.com", path = c("v3", "businesses", "search"),
               query = list(term = "coffee",
                            location = "Vancouver, BC", limit = 3)))
#> [1] "https://api.yelp.com/v3/businesses/search?term=coffee&location=Vancouver%2C%20BC&limit=3"
res <- GET(url, add_headers('Authorization' = paste("bearer", token)))
http_status(res)
#> $category
#> [1] "Success"
#> 
#> $reason
#> [1] "OK"
#> 
#> $message
#> [1] "Success: (200) OK"
ct <- content(res)
sapply(ct$businesses, function(x) x[c("name", "phone")])
#>       [,1]           [,2]                          [,3]                  
#> name  "Revolver"     "Timbertrain Coffee Roasters" "49th Parallel Coffee"
#> phone "+16045584444" "+16049159188"                "+16048724901"

I've managed to use oauth2.0_token() like so:

library(httr)

yelp_app <- oauth_app("yelp", key = Sys.getenv("YELP_ID"),
                      secret = Sys.getenv("YELP_SECRET"))
## API docs offer nothing useful about what to put here?
## I would like to NOT give an `authorize` argument
## but oauth_endpoint() requires it
yelp_endpoint <-
  oauth_endpoint(NULL,
                 authorize = "https://api.yelp.com/oauth2/token",
                 access = "https://api.yelp.com/oauth2/token")
## just enter anything for the authorization code
token <- oauth2.0_token(yelp_endpoint, yelp_app,
                        user_params = list(grant_type = "client_credentials"),
                        use_oob = TRUE)

At this point, I simply hit return and provide no input:

Please point your browser to the following url: 

  https://api.yelp.com/oauth2/token?client_id=XXX&scope=&redirect_uri=urn%3Aietf%3Awg%3Aoauth%3A2.0%3Aoob&response_type=code

Enter authorization code: 

The token is obtained and cached to .httr-oauth. And I can use it as usual.

res2 <- GET(url, config(token = token))
ct2 <- content(res2)
sapply(ct2$businesses, function(x) x[c("name", "phone")])
      [,1]           [,2]                          [,3]                  
name  "Revolver"     "Timbertrain Coffee Roasters" "49th Parallel Coffee"
phone "+16045584444" "+16049159188"                "+16048724901"
@cderv

This comment has been minimized.

Contributor

cderv commented Dec 16, 2016

Glad we find another use case, I had difficulties to find a public API on this case. Mine was on data.rte-france.com.

I had difficulties with token refresh too as the token I obtained is indeed cached but is temporary (7200 seconds in my case). I will test with Yelp API if is the same. If you have input on that, I am willing to discuss it.

@jennybc

This comment has been minimized.

Member

jennybc commented Dec 16, 2016

The Yelp tokens allegedly are valid for 180 days (!!).

@hadley hadley closed this in #463 Aug 1, 2017

hadley added a commit that referenced this issue Aug 1, 2017

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment