Embracing the Messiness in Search of Epic Solutions

MSAL: Caching Access Token

Posted

in

,

Why Cache Access Token with MSAL?

Building upon the previous post that performs delegated access authentication with MSAL, suppose your program uses this function to get the access token.

def get_access_token():
    app = msal.PublicClientApplication(CLIENT_ID, authority=AUTHORITY)
    result = app.acquire_token_interactive(scopes=SCOPES)
    access_token = result['access_token']

    return access_token

In this case, it will always launch the browser to complete the authentication flow and retrieve the access token every time the program runs.

While it works, it introduces a slight latency to your program. Fortunately, MSAL provides the capability to cache the access token.

Solution A: Use custom persistence cache

In this solution, we can introduce our custom persistence cache.

First, we can use these functions to load the access token from the cache file and save it to the cache file.

import os
import msal

...
CACHE_FILE = 'msal_token_cache.json'

def load_cache():
    cache = msal.SerializableTokenCache()

    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, 'rb') as f:
            cache.deserialize(f.read())

    return cache

def save_cache(cache):
    if cache.has_state_changed:
        with open(CACHE_FILE, 'wb') as f:
            f.write(cache.serialize().encode())

We can first attempt to get a valid access token from the cache. It will only get the token interactively via the browser if the access token doesn’t exist or has expired.

def get_access_token():
    cache = load_cache()
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=cache,
    )
    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts[0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    save_cache(cache)
    access_token = result['access_token']

    return access_token

While this solution works, the implementation code is very lengthy. Furthermore, the access token is stored in the cache file as clear text.

Solution B: Use the OS platform’s encrypted vault (PREFERRED)

A Python package called msal-extensions allows the access token to be stored securely in the OS platform’s encrypted vault. For example, If the program runs on MacOS, KeyChain will be used. Windows uses DPAPI, and Linux uses LibSecret.

To begin, install that Python package.

# requirements.txt

msal
msal-extensions

The configuration is much simpler than Solution A.

import msal
from msal_extensions import build_encrypted_persistence, PersistedTokenCache

...
CACHE_FILE = 'msal_token_cache.json'

def get_access_token():
    app = msal.PublicClientApplication(
        CLIENT_ID,
        authority=AUTHORITY,
        token_cache=PersistedTokenCache(build_encrypted_persistence(CACHE_FILE)),
    )

    result = None
    accounts = app.get_accounts()

    if accounts:
        result = app.acquire_token_silent(SCOPES, account=accounts[0])

    if not result:
        result = app.acquire_token_interactive(scopes=SCOPES)

    access_token = result['access_token']

    return access_token

When running the program on MacOS, the access token is stored in the KeyChain.

Note: An empty cache file named msal_token_cache.json has also been created. Unfortunately, there is no option in msal-extensions==1.2.0 to disable the creation of this file at the moment.

Comments

Leave a Reply