Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for custom storage of oauth2 tokens #78

Conversation

sangaline
Copy link
Contributor

This implements support for using an external storage mechanism for oauth2 tokens to prevent contention issues when using multiple clients in different processes. The proposal was written out in #72, but I made some slight changes that felt natural when writing the code. This implementation replaces BaseClient.access_token and BaseClient.refresh_token with properties that defer to BaseClient.__access_token and BaseClient.__refresh_token when no external storage is configured. This behavior should be fully backwards compatible with how the library worked before this PR.

There are two new options that you can specify when initializing the base client that will change this default behavior: oauth2_token_getter: Optional[Callable[[Literal['access_token', 'refresh_token'], str], str]] = None and oauth2_token_setter: Optional[Callable[[Literal['access_token', 'refresh_token'], str, str], None]] = None. They both take the token type as the first argument and the client ID as the second argument. The setter has one additional argument that is the value of the token. These methods both default to None, but are used instead of deferring to BaseClient.__access_token and BaseClient.__refresh_token when the are provided. This allows using whichever shared storage mechanism you see fit to manage the tokens. If no tokens are retrieved from the getter method, then the properties will fall back to the internal variables. Even if the access_token is expired, this allows the client to get new tokens when the external storage is expired or otherwise unavailable.

Here's an example of how you can use the new functionality to store your oauth2 tokens in redis:

import aioredis
from hubspot3 import Hubspot3


redis_client = await aioredis.create_redis_pool(url, db=db, encoding='utf-8', timeout=10)

def oauth2_token_getter(token_type: str, client_id: str) -> str:
    loop = asyncio.get_event_loop()
    key = f'hubspot-oauth2-tokens:{token_type}:{client_id}'
    return loop.run_until_complete(redis_client.get(key))

def oauth2_token_setter(token_type: str, client_id: str, token: str) -> None:
    loop = asyncio.get_event_loop()
    key = f'hubspot-oauth2-tokens:{token_type}:{client_id}'
    # Token expiration is six hours, so match that when we store the tokens.
    # See: https://developers.hubspot.com/docs/methods/oauth2/refresh-access-token
    expire_in_seconds = 6 * 60 * 60
    loop.run_until_complete(redis_client.set(key, token, expire=expire_in_seconds))

# This client will share oauth2 credentials with other clients configured in the same way.
hubspot3_client = Hubspot3(
    access_token=access_token,
    client_id=client_id,
    client_secret=client_secret,
    refresh_token=refresh_token,
    oauth2_token_getter=oauth2_token_getter,
    oauth2_token_setter=oauth2_token_setter,
)

Closes #72

@sangaline
Copy link
Contributor Author

Note that these new init arguments and attributes broke the pylint checking. I added exceptions to the too-many-arguments and too-many-instance-attributes rules in BaseClient to get tests to pass, not sure if you want to handle this in a different way.

@jpetrucciani
Copy link
Owner

Awesome! I'm out at AWS re:Invent right now, but I'll try to get around to reviewing and merging ASAP!

@jpetrucciani jpetrucciani merged commit 0046b70 into jpetrucciani:master Dec 7, 2019
@jpetrucciani
Copy link
Owner

This is now live on pip as version 3.2.40

Thanks again for your contributions! 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Proposal to improve support for oauth2 authentication with multiple clients
2 participants