pythonoauth-2.0python-requestswrapperclientcredential

Automatic token fetching with OAuth2 client_credentials flow with Python Requests


I want to use an API that is authenticated with the OAuth2 client_credentials flow from Python.

In pyhton the most widely used HTTP client is Requests, and Requests has many advanced features and extensions, some of which revolve around using it with OAuth2.

However, Oauth2 is a complex beast that support 4 different flows, of which client_credentials is the simplest. Try as I might, I have not found a simple to-the-point example of using Requests with the client_credentials flow. It is always mentioned at the end of the article as a sidenote or afterthought after explaining all the othe flows in intricated detail.

In client_credentials flow, the following will happen:

1. POST the following encoded as x-www-form-urlencoded to the "token endpoint":

{
        "grant_type": "client_credentials",
        "client_id": client_id,
        "client_secret": client_secret,
        "scope": "all", # Or whatever scope you care about
}

where the client_id and client_secret are provided by the service you are accessing.

2. The following will be returned as json (given the auth was successful):

{
    "token_type":"Bearer",
    "scope":"openid email profile offline_access roles read:tickets read:customers ...",
    "access_token":"XXX_THIS_TOKEN_IS_VERY_SECRET_XXX"
}

3. Use "access_token" from returned data to access protected resources:

You use it by setting it in the Authorization: Bearer header in all requests to protected resources in the following way:

Authorization: Bearer XXX_THIS_TOKEN_IS_VERY_SECRET_XXX

The token will last 1 hour from being generated and when a resource request returns 401 auth errors, simply repeat steps #1 and #2 to generate a new token to use.

My current code manually requests a token from the token endpoint, parses the token from the resulting json and stuffs that into subsequent requests' HTTP headers. I then proceed to detect 401 errors and re-authenticate. This works but it is very tediouos. I now have to wrap every request usage with this ugly mess.

I was hoping there would be a way to do it similar to this:

client = MagicRequestsWrapper(client_id=client_id, client_secret=client_secret, token_endpoint=token_endpoint, scope=scope)

res = client.get(protected_resource_endpoint)

where all the oauth stuff is now hidden and client behaves exactly like a normal Requests client.

How can I do that?


Solution

  • I found the answer shortly after writing this question.

    import requests
    from requests_oauth2client import OAuth2Client, OAuth2ClientCredentialsAuth
    
    def oauth2_session(client_id, client_secret, base_url, scope="all"):
        token_url = f"{base_url}/auth/token" # This may be different for your endpoint
    
        oauth2client = OAuth2Client( token_endpoint=token_url, client_id=client_id, client_secret=client_secret)
        auth = OAuth2ClientCredentialsAuth( oauth2client, scope=scope, resource=base_url)
    
        session = requests.Session()
        session.auth = auth
        return session
    

    Example usage as follows:

    # Create a session
    base_url = "https://myservice.com"
    my_session = oauth2_session(client_id=os.environ.get("CLIENT_ID"), client_secret=os.environ.get("CLIENT_SECRET"), base_url=base_url)
    
    # Use it as normal requests object to post and get
    result = my_session.get(f"{base_url}/api/some_resource")
    
    my_session.post(f"{base_url}/api/some_resource", json={"test":123})