4

When running the following (while replacing example.com with our API, obviously)

req = Request('GET', 'https://example.com')
# client is a customized OAuth2Session
client.authorize(self.username, self.password, self.auth_key)
print(self.client.authorized) # True

the following returns <Response [200]>:

response = client.request(req.method, req.url)

But this returns <Response [401]>:

 prepped = client.prepare_request(req)
 response = client.send(prepped)

How can I reuse the original Request object when sending it through the OAuth2Session?

hielsnoppe
  • 2,819
  • 3
  • 31
  • 56

1 Answers1

5

The OAuth2Session implementation doesn't override the Session.prepare_request() or Session.send() methods, only Session.request() has been specialised. That's because it handles auto-refreshing in the same method, requiring more requests to be sent out.

To support altering those requests, the library offers a compliance hooks facility, where at specific points in the process a hook is called that can alter request details. The OAuth2Session object, since version 0.4.0, supports 3 different hooks:

  • access_token_response: passed the response from a access token request, before the response is parsed to extract the token.
  • refresh_token_response: passed the response from a refresh token request, again before the response is parsed.
  • protected_request: passed the url, headers and body of requests used to access protected resources (so a request that should include a valid token).

Several included compliance fixers use these hooks to add missing elements from responses from certain providers, and to update outgoing requests when certain APIs deviate from the OAuth standard as to how they handle tokens.

protected_request is the interesting hook here, as it is passed the same data you would normally want to alter when using a request.Request() / session.prepare_request() / session.send() pattern. You get to alter the same request data in slightly different packaging, before the oauthlib client gets to add the token to that data.

That said, if you don't need to use auto-refreshing or can handle token expiration yourself, you can access the oauthlib client that the OAuth2Sesson wraps, directly. Provided you already fetched a token, you can sign your request, before preparing, with:

from oauthlib.oauth2 import TokenExpiredError

req = Request('GET', 'https://example.com')
try:
    req.url, req.headers, req.data = client._client.add_token(
        req.url, http_method=req.method, body=req.data, headers=req.headers
    )
except TokenExpiredError:
    # handle token expiration
    pass

else:
    prepped = client.prepare_request(req)
    response = client.send(prepped)

This uses the oauthlib client's add_token() method directly.

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • Having a prepared request like this: `req = requests.Request('GET', BASE_URL, params=params)` how can I then send it? I used `dir(req)` but it seems it doesn't have a `send` method. Do I necessarily need to pass it through a `Session` object? Is this what `requests.get` / `requests.post` do behind the curtains? They create a session and passed the prepped request to the session? – M.Ionut Jun 10 '22 at 13:14
  • 1
    @M.Ionut: yes, you have to pass in the prepared request to `session.request()`. That's what the sample code in my answer does (`client` is an instance of `OAuth2Session` here, which is a subclass of `Session`). – Martijn Pieters Jul 08 '22 at 12:06
  • 1
    @M.Ionut: under the covers, `Session.request()` creates a `Request` instance, uses `Session.prepare()` to turn that into a `PreparedRequest`, then passes the latter to `Session.send()`. `Session.request()` is what `Session.get()` / `Session.post()`, etc. all defer to. – Martijn Pieters Jul 08 '22 at 12:09