This class is one of my favorite simple patterns to implement when writing Python code against a web API. It’s a class which maintains the lifecycle of an OAuth bearer token for you, so that each time you use the token elsewhere in your code, you get a valid token back.
The class makes the following assumptions:
The class was originally built against the Azure graph API using the msal
library, but you can adapt it to use the SDK of any app. For our example, I’ve used a generic app with the requests
library.
Here’s the entire class:
import requests
import datetime
class MagicToken:
config = {
"client_id": "123456",
"client_secret": "shh",
"scope": [ "read", "write", "etc" ],
"auth_endpoint": "<https://api.my-great-app.com/v1.0/auth>"
}
def __init__(self):
self.__acquire_token()
def __acquire_token(self):
payload = {
"client_id" : config['client_id'],
"client_secret" : config['client_secret'],
"scope" : config['scope']
}
url = config['auth_endpoint']
response = requests.post(url, data=payload)
self.__access_token = result['access_token']
self.__token_expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=result['expiration'])
return
@property
def token(self):
if self.__token_expires > datetime.datetime.now(datetime.timezone.utc):
return self.__access_token
else:
self.__acquire_token()
return self.__access_token
Let’s break it down piece by piece.
config = {
"client_id": "123456",
"client_secret": "shh",
"scope": [ "read", "write", "etc" ],
"auth_endpoint": "<https://api.my-great-app.com/v1.0/auth>"
}
This is the basic configuration required to get our token. Client ID, client secret, and the authorization endpoint.
def __init__(self):
self.__acquire_token()
def __acquire_token(self):
payload = {
"client_id" : config['client_id'],
"client_secret" : config['client_secret'],
"scope" : config['scope']
}
url = config['auth_endpoint']
response = requests.post(url, data=payload)
self.__access_token = result['access_token']
self.__token_expires = datetime.datetime.now(datetime.timezone.utc) + datetime.timedelta(seconds=result['expiration'])
return
When we first start our app, we need to acquire a token. The __acquire_token()
method starts with __
in order to indicate that it shouldn’t be called from elsewhere in our code, it’s effectively a “private” that should only be called from within our class.
When we receive our OAuth response from the server, we want to capture the token itself, as well as the expiration time. Most servers give you back a number of seconds until expiration. If there’s a refresh token as well, you might need to use that to get your next token. We convert this number of seconds into a datetime
object, in the future from the current time.
@property
def token(self):
if self.__token_expires > datetime.datetime.now(datetime.timezone.utc):
return self.__access_token
else:
self.__acquire_token()
return self.__access_token
This is where the magic comes in. We can use Python’s @property decorator to handle the lifecycle of the token. Whenever your other code needs to use this token, you can do it like this:
access_token = MagicToken()
def get_user(id):
headers = {
"Authorization": "Bearer " + access_token.token,
"Content-Type": "application/json"
}
url = "<https://api.my-great-app.com/v1.0/users/>" + id"
response = requests.get(url, headers=headers)
return response
access_token.token
is now a computed property. If you request it, the token()
function will determine if the token is expired or not. If it’s still valid, it will return the token. If not, it will first acquire a new token, then return the new token.
We only need a getter for this property, since nothing outside of the class needs to be able to set the token.
And now, your token will magically manage itself, leaving you free to write good code against your favorite API.
Mastodon