Utilizing Django Cache for Office 365's Token Backend

Tuesday, February 13, 2024

Microsoft's O365 python library offers developers a quick an easy tool for interacting with Graph APIs. I won't get into details here, but I highly recommend that you check out the documentation for its capabilities. In this post, I will be covering the library's ability to utilize a custom token cache backend and how you can hook it into Django's cache.

Before getting started, ensure that the Django cache framework is set up. In my project, I decided to use Memcached with the following configuration:

CACHES = {
    "default": {
        "BACKEND": "django.core.cache.backends.memcached.PyMemcacheCache",
        "LOCATION": "{0}:{1}".format(env('CACHE_HOST'), env('CACHE_PORT')),
    }
}

Now we can create our custom token backend. The first step is to set up a sublcass that inherits from BaseTokenBackend:

from O365.utils import BaseTokenBackend

class DjangoCacheTokenBackend(BaseTokenBackend):

    def __init__(self):
        super().__init__()

Now to actually utilize the cache, two methods must be implemented:

  1. load_token: Loads the token from the backend and will either return a Token instance or None
  2. save_token: Stores the token in the backend

I also recommend implementing the following optional methods:

  1. delete_token: Deletes the token from the backend
  2. check_token: Returns a boolean representing the presence of the token in the backend

 

With the Django cache framework setup, the default cache can be imported and used within the class. A default key token_key is set up in the constructor and it will be used with all interactions. The following code contains the entire Django cache token backend:

from django.core.cache import cache
from O365.utils import BaseTokenBackend

class DjangoCacheTokenBackend(BaseTokenBackend):

    def __init__(self):
        super().__init__()
        self.token_key = 'token'

    def load_token(self):
        token = cache.get(self.token_key)
        if token:
            token = self.token_constructor(self.serializer.loads(token))
        return token
    
    def save_token(self):
        if self.token is None:
            raise ValueError('You have to set the "token" first.')

        cache.set(self.token_key, self.serializer.dumps(self.token), 3600)

        return True
    
    def delete_token(self):
        cache.delete(self.token_key)
        return True
    
    def check_token(self):
        return cache.get(self.token_key) is not None

 

When setting up the account with o365, simply import the class, initiatlize it and pass it as the token_backend parameter:

from .token import DjangoCacheTokenBackend

credentials = (env('O365_CLIENT'), env('O365_SECRET'))
token_backend = DjangoCacheTokenBackend()
account = Account(credentials, 
        auth_flow_type='credentials', 
        tenant_id=env('O365_TENANT'),
        token_backend=token_backend,
        )
Running on recycled hardware from my closet