stanford.green.oauth2

Library to interact with OAuth2 endpoints.

Overview

The stanford.green.oauth2 package provides classes to connect to an OAuth2 Authorization Server and get an access token. These classes support retries via the exponential_backoff_ca Python package. Once you have the access token it is up to you to use it to make API calls.

There is built-in file-based caching to minimize the number of times you have to go out to the access token endpoint.

Examples

To connect to an OAuth2 Authorization Server and get an access token:

from exponential_backoff_ca import ExponentialBackoff
from stanford.green.oauth2  import AccessToken, ApiAccessTokenEndpoint

# The URL to get the token (provided by OAuth2 Authorization Service):
url = "https://api.endpoint.com/api/v1/token"

# The ApiAccessTokenEndpoint object requires an exponential backoff
# object to do the retries:
time_slot_secs = 3.0  # The number of seconds in each time slot.
num_iterations = 4    # The number of iterations.
limit_value    = 10.0 # Don't wait any longer than this number of seconds.
exp_backoff    = ExponentialBackoff(time_slot_secs, num_iterations,
                                    limit_value=limit_value, debug=True)

# Define the ApiAccessTokenEndpoint object. The 'oauth2' tells the
# ApiAccessTokenEndpoint class that this is an OAuth2 access token
# endpoint.
client_id     = 'username'
client_secret = 'password'
api_access    = ApiAccessTokenEndpoint('oauth2', url, client_id, client_secret,
                                        exp_backoff, scopes=['read', 'list'],
                                        verbose=True)

# If you want to cache the token, set the "use_cache" flag to True:
# api_access = ApiAccessTokenEndpoint('oauth2', url, client_id, client_secret,
#                                     exp_backoff,  scopes=['read', 'list'],
#                                     verbose=True, use_cache=True)

# Get the token.
access_token = api_access.get_token()

# This token can now be used with the API to do other operations.

To connect to an ACS-style API endpoint you use much the same code as above:

from exponential_backoff_ca import ExponentialBackoff
from stanford.green.oauth2  import AccessToken, ApiAccessTokenEndpoint

# The URL to get the token (provided by OAuth2 Authorization Service):
url = "https://api.endpoint.com/api/v1/token"

# The ApiAccessTokenEndpoint object requires an exponential backoff
# object to do the retries:
time_slot_secs = 3.0  # The number of seconds in each time slot.
num_iterations = 4    # The number of iterations.
limit_value    = 10.0 # Don't wait any longer than this number of seconds.
exp_backoff    = ExponentialBackoff(time_slot_secs, num_iterations,
                                    limit_value=limit_value, debug=True)

# Define the ApiAccessTokenEndpoint object. The 'acs_api' tells the
# ApiAccessTokenEndpoint class that this is ACS API-style endpoint.
# Note that this kind of API access endpoint does not supply the
# scopes parameter.
client_id     = 'username'
client_secret = 'password'
api_access    = ApiAccessTokenEndpoint('acs_api', url, client_id, client_secret,
                                        exp_backoff, verbose=True)

# Get the token.
access_token = api_access.get_token()

# This token can now be used with the API to do other operations.
class stanford.green.oauth2.AccessToken(token: str, expires_at: datetime)

An object representing an OAuth access token returned by an OAuth Authorization Server.

Parameters:
  • token (str) – the token string returned by an OAuth Authorization Server.

  • expires_at (datetime.datetime) – the date and time when the token token expires.

property expires_at: datetime

Return the expires_at property (datetime.datetime object when token expires)

expires_in() int

The number of seconds until access token expires.

Returns:

the number of seconds until access token expires

Return type:

int

Note: if the token has expired this value will be negative.

is_expired() bool

Has the token expired?

Returns:

True if the token has expired, False otherwise.

Return type:

bool

zulu_time_string() str

Return the _expires_at property as a Zulu time string

class stanford.green.oauth2.ApiAccessTokenEndpoint(endpoint_type: str, url: str, client_id: str, client_secret: str, exp_backoff: ExponentialBackoff, timeout: float = 15.0, use_cache: bool = True, verbose: bool = False, grant_type: str = 'client_credentials', scopes: list[str] = [])

Represents an API endpoint returning an access token.

Parameters:
  • endpoint_type (str) –

    the type of API Access endpoint. Two types of endpoints are currently recognized:

    • acs_api: a Stanford ACS-style API endpoint

    • oauth2: a generic OAuth2 Authorization token endpoint

  • url (str) – the URL pointing to the OAuth Server’s access token endpoint. This is where we go to get the access token.

  • client_id (str) – the OAuth client’s identifier

  • client_secret (str) – the OAuth client’s secret (i.e., password)

  • exp_backoff (ExponentialBackoff) – an ExponentialBackoff object used for retrying access token retrieval.

  • timeout (float) – the maximum time in seconds to wait for each request attempt; default: 15.0.

  • use_cache (bool) – if set to True the access token will be cached; default: True.

  • verbose (bool) – if set to True progress information will be sent to standard output; default: False.

  • grant_type (str) – (only relevant if endpoint type is “oauth2”) the OAuth grant type; default: “client_credentials”

  • scopes (list[str]) – (only relevant if endpoint type is “oauth2”) a list of OAuth scopes the client wants access to; default: the empty list

_get_token() AccessToken

Get the access token from the token endpoint.

This is a simple wrapper function that calls the appropriate get-token function depending on the value of self.endpoint_type.

_get_token_acs_api() AccessToken

Get an access token from an ACS-API compatible token endpoint (no caching)

The JSON response from the ACS API token endpoint contains the token itself and two time-related attributes:

  • expires_at: when the token expires in Zulu (UTC) time.

  • expires_in: the number of seconds from when the token was generated until it expires.

When creating the AccessToken object we convert the Zulu time expires_at string into a Python timezone-aware datetime object that AccessToken requires.

Furthermore, AccessToken calculates expires_in itself so we ignore the response’s expires_in value.

_get_token_oauth2() AccessToken

Get an access token from an OAuth2 endpoint (no caching)

The JSON response contains the token itself and the expires_in attribute

  • expires_in: the number of seconds from when the token was generated until it expires.

cache_get() AccessToken

Get the cached value.

Returns:

the cached AccessToken object.

Return type:

AccessToken

cache_set(value: AccessToken, expires_in: int) None

Cache the AccessToken object.

Parameters:
  • value (AccessToken) – the AccessToken to cache.

  • expires_in (int) – set the expiration to be expires_in seconds from now.

We use a file-based Cache which pickles the object before storage. To support this the AccessToken object has a custom Pickle instance (see __setstate__ and __getstate in the AccessToken class in the source code).

Note: this method only relevant if self.use_cache is True.

get_token(expires_at_override: datetime | None = None) AccessToken

Get access token (uses cache if enabled).

Parameters:

expires_at_override (Optional[datetime.datetime]) – a datetime.datetime to use instead of the actual expiration time; defaults to None.

Returns:

a valid (cached or otherwise) AccessToken object.

Return type:

AccessToken

If the value is cached, uses the cached value, otherwise gets the access token using _get_token().

There are circumstances (e.g., during unit testing) when we want to override the expires_at time that was set by the token API call. For those circumstances use the expires_at_override parameter.

is_acs_api() bool
Returns:

True if self.endpoint_type is set to “acs_api”, False otherwise.

Return type:

bool

is_oauth2() bool
Returns:

True if self.endpoint_type is set to “oauth2”, False otherwise.

Return type:

bool

progress(msg: str) None

Show a progress message