Skip to content

acquire_token_by_refresh_token() for RT migration #193

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

Merged
merged 2 commits into from
May 14, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions msal/application.py
Original file line number Diff line number Diff line change
Expand Up @@ -700,6 +700,36 @@ def _validate_ssh_cert_input_data(self, data):
"you must include a string parameter named 'key_id' "
"which identifies the key in the 'req_cnf' argument.")

def acquire_token_by_refresh_token(self, refresh_token, scopes):
"""Acquire token(s) based on a refresh token (RT) obtained from elsewhere.

You use this method only when you have old RTs from elsewhere,
and now you want to migrate them into MSAL.
Calling this method results in new tokens automatically storing into MSAL.

You do NOT need to use this method if you are already using MSAL.
MSAL maintains RT automatically inside its token cache,
and an access token can be retrieved
when you call :func:`~acquire_token_silent`.

:param str refresh_token: The old refresh token, as a string.

:param list scopes:
The scopes associate with this old RT.
Each scope needs to be in the Microsoft identity platform (v2) format.
See `Scopes not resources <https://docs.microsoft.com/en-us/azure/active-directory/develop/migrate-python-adal-msal#scopes-not-resources>`_.

:return:
* A dict contains "error" and some other keys, when error happened.
* A dict contains no "error" key means migration was successful.
"""
return self.client.obtain_token_by_refresh_token(
refresh_token,
decorate_scope(scopes, self.client_id),
rt_getter=lambda rt: rt,
on_updating_rt=False,
)


class PublicClientApplication(ClientApplication): # browser app or mobile app

Expand Down
67 changes: 67 additions & 0 deletions sample/migrate_rt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
"""
The configuration file would look like this:

{
"authority": "https://login.microsoftonline.com/organizations",
"client_id": "your_client_id",
"scope": ["User.ReadBasic.All"],
// You can find the other permission names from this document
// https://docs.microsoft.com/en-us/graph/permissions-reference
}

You can then run this sample with a JSON configuration file:

python sample.py parameters.json
"""

import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
import json
import logging

import msal


# Optional logging
# logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
# logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs

def get_preexisting_rt_and_their_scopes_from_elsewhere():
# Maybe you have an ADAL-powered app like this
# https://github.com/AzureAD/azure-activedirectory-library-for-python/blob/1.2.3/sample/device_code_sample.py#L72
# which uses a resource rather than a scope,
# you need to convert your v1 resource into v2 scopes
# See https://docs.microsoft.com/azure/active-directory/develop/azure-ad-endpoint-comparison#scopes-not-resources
# You may be able to append "/.default" to your v1 resource to form a scope
# See https://docs.microsoft.com/azure/active-directory/develop/v2-permissions-and-consent#the-default-scope

# Or maybe you have an app already talking to Microsoft identity platform v2,
# powered by some 3rd-party auth library, and persist its tokens somehow.

# Either way, you need to extract RTs from there, and return them like this.
return [
("old_rt_1", ["scope1", "scope2"]),
("old_rt_2", ["scope3", "scope4"]),
]


# We will migrate all the old RTs into a new app powered by MSAL
config = json.load(open(sys.argv[1]))
app = msal.PublicClientApplication(
config["client_id"], authority=config["authority"],
# token_cache=... # Default cache is in memory only.
# You can learn how to use SerializableTokenCache from
# https://msal-python.rtfd.io/en/latest/#msal.SerializableTokenCache
)

# We choose a migration strategy of migrating all RTs in one loop
for old_rt, scopes in get_preexisting_rt_and_their_scopes_from_elsewhere():
result = app.acquire_token_by_refresh_token(old_rt, scopes)
if "error" in result:
print("Discarding unsuccessful RT. Error: ", json.dumps(result, indent=2))

print("Migration completed")

# From now on, those successfully-migrated RTs are saved inside MSAL's cache,
# and becomes available in normal MSAL coding pattern, which is NOT part of migration.
# You can refer to:
# https://github.com/AzureAD/microsoft-authentication-library-for-python/blob/1.2.0/sample/device_flow_sample.py#L42-L60