lostisland / faraday

Simple, but flexible HTTP client library, with support for multiple backends.
https://lostisland.github.io/faraday
MIT License
5.72k stars 972 forks source link

Faraday unknown request issue #1461

Closed danieldraper closed 1 year ago

danieldraper commented 1 year ago

Basic Info

Issue description

Unsure exactly how to describe the issue, the response would seem to imply the authorization isn't working as expected however I'm not convinced that is the problem.

I've completed the same PayPal request 3 times to verify it does in fact work and this is an issue with Faraday.

The initial attempt uses the example request from the API docs.

curl -v "https://api-m.sandbox.paypal.com/v1/oauth2/token" \
    -u "<CLIENT_ID>:<CLIENT_SECRET>" \
    -H "Content-Type: application/x-www-form-urlencoded" \
    -d "grant_type=client_credentials"

Response

* Connected to api-m.sandbox.paypal.com (151.101.65.35) port 443 (#0)
* REDACTED (for brevity)
* h2h3 [:method: POST]
* h2h3 [:path: /v1/oauth2/token]
* h2h3 [:scheme: https]
* h2h3 [:authority: api-m.sandbox.paypal.com]
* h2h3 [authorization: Basic REDACTED]
* h2h3 [user-agent: curl/7.84.0]
* h2h3 [accept: */*]
* h2h3 [content-type: application/x-www-form-urlencoded]
* h2h3 [content-length: 29]
> POST /v1/oauth2/token HTTP/2
> Host: api-m.sandbox.paypal.com
> authorization: Basic REDACTED
> user-agent: curl/7.84.0
> accept: */*
> content-type: application/x-www-form-urlencoded
> content-length: 29
>
* We are completely uploaded and fine
< HTTP/2 200
< content-type: application/json
< server: nginx/1.14.0 (Ubuntu)
< cache-control: max-age=0, no-cache, no-store, must-revalidate
< paypal-debug-id: REDACTED
< pragma: no-cache
< x-paypal-token-service: IAAS
< strict-transport-security: max-age=31536000; includeSubDomains
< accept-ranges: bytes
< via: 1.1 varnish, 1.1 varnish
< edge-control: max-age=0
< date: Tue, 15 Nov 2022 02:03:31 GMT
< x-served-by: REDACTED, REDACTED
< x-cache: MISS, MISS
< x-cache-hits: 0, 0
< x-timer: REDACATED
< vary: Accept-Encoding
< content-length: 1115
<
* Connection #0 to host api-m.sandbox.paypal.com left intact
{"scope":"REDACTED","access_token":"REDACTED","token_type":"Bearer","app_id":"REDACTED","expires_in":32355,"nonce":"REDACTED"}

Using Ruby Net::HTTP

url = URI("https://api-m.sandbox.paypal.com/v1/oauth2/token")

https = Net::HTTP.new(url.host, url.port)
https.use_ssl = true

request = Net::HTTP::Post.new(url)
request.basic_auth client_id, client_secret
request["Content-Type"] = "application/x-www-form-urlencoded"
request.body = "grant_type=client_credentials"

response = https.request(request)
puts response.read_body

Response

{"scope":"REDACTED","access_token":"REDACTED","token_type":"Bearer","app_id":"REDACTED","expires_in":32031,"nonce":"REDACTED"}

Using Faraday (I've also attempted setting the headers for Authorization and Content-Type when initializing Faraday)

connection = Faraday.new(url: "https://api-m.sandbox.paypal.com/v1") do |conn|
  conn.request :authorization, :basic, client_id, client_secret
  conn.request :url_encoded
  conn.response :logger
end

response = connection.post("/oauth2/token", {grant_type: "client_credentials"})

puts response.body

Response

I, [2022-11-15T12:09:54.552847 #93046]  INFO -- request: POST https://api-m.sandbox.paypal.com/oauth2/token
I, [2022-11-15T12:09:54.552918 #93046]  INFO -- request: User-Agent: "Faraday v2.6.0"
Authorization: "Basic REDACTED"
Content-Type: "application/x-www-form-urlencoded"
I, [2022-11-15T12:09:54.646325 #93046]  INFO -- response: Status 403
I, [2022-11-15T12:09:54.646387 #93046]  INFO -- response: connection: "close"
content-length: "417"
server: "Varnish"
retry-after: "0"
content-type: "text/html; charset=utf-8"
accept-ranges: "bytes"
date: "Tue, 15 Nov 2022 02:09:54 GMT"
via: "1.1 varnish"
x-served-by: "REDACTED"
x-cache: "MISS"
x-cache-hits: "0"
x-timer: "REDACTED"

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html>
  <head>
    <title>403 Forbidden</title>
  </head>
  <body>
    <h1>Error 403 Forbidden</h1>
    <p>Forbidden</p>
    <h3>Error 54113</h3>
    <p>Details: REDACTED REDACTED REDACTED</p>
    <hr>
    <p>Varnish cache server</p>
  </body>
</html>

The headers looks good (used a Proxy to capture).

CleanShot 2022-11-15 at 12 46 21@2x

I also compared the Basic auth headers to ensure the values are the same, can confirm the basic auth header generated by Faraday matches basic auth header from Curl and Net::HTTP.

CleanShot 2022-11-15 at 12 16 26@2x

CleanShot 2022-11-15 at 12 15 34@2x

Steps to reproduce

  1. Create a PayPal account
  2. Setup API details https://developer.paypal.com/dashboard/applications
  3. Create a local Ruby script using this Gist
  4. Update the script with your credentials
  5. Run the scrip ruby <SCRIPT_NAME>.rb
iMacTia commented 1 year ago

@danieldraper your Faraday request is calling the incorrect URL: https://api-m.sandbox.paypal.com/oauth2/token missing the v1 before /oauth2. The reason is that when you're making the call, you're doing the following:

connection.post("/oauth2/token", ...)

Passing a path that starts with / is a way to tell Faraday that you want to override the base path specified in the connection initialisation. If you want to keep the v1, you need to make the request in this way:

connection.post("oauth2/token", ...)

Removing the slash should be enough to make the request work 👍

P.S.: Thank you for the very detailed ticket description, snippets and steps to reproduce 👏 !

danieldraper commented 1 year ago

🤦 Thanks @iMacTia always the small things.