Skip to content

Commit fde6129

Browse files
committed
Merge branch 'demo-global-token-cache' into dev
2 parents 18d3473 + 8bab1dd commit fde6129

7 files changed

+270
-204
lines changed

msal/application.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,7 @@ def __init__(
280280
281281
:param bool validate_authority: (optional) Turns authority validation
282282
on or off. This parameter default to true.
283-
:param TokenCache cache:
283+
:param TokenCache token_cache:
284284
Sets the token cache used by this ClientApplication instance.
285285
By default, an in-memory cache will be created and used.
286286
:param http_client: (optional)

sample/confidential_client_certificate_sample.py

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
3232
import json
3333
import logging
34+
import time
3435

3536
import requests
3637
import msal
@@ -42,27 +43,37 @@
4243

4344
config = json.load(open(sys.argv[1]))
4445

45-
# Create a preferably long-lived app instance which maintains a token cache.
46-
app = msal.ConfidentialClientApplication(
46+
# If for whatever reason you plan to recreate same ClientApplication periodically,
47+
# you shall create one global token cache and reuse it by each ClientApplication
48+
global_token_cache = msal.TokenCache() # The TokenCache() is in-memory.
49+
# See more options in https://msal-python.readthedocs.io/en/latest/#tokencache
50+
51+
# Create a preferably long-lived app instance, to avoid the overhead of app creation
52+
global_app = msal.ConfidentialClientApplication(
4753
config["client_id"], authority=config["authority"],
4854
client_credential={"thumbprint": config["thumbprint"], "private_key": open(config['private_key_file']).read()},
49-
# token_cache=... # Default cache is in memory only.
50-
# You can learn how to use SerializableTokenCache from
51-
# https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
55+
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
56+
# If absent, ClientApplication will create its own empty token cache
5257
)
5358

54-
# Since MSAL 1.23, acquire_token_for_client(...) will automatically look up
55-
# a token from cache, and fall back to acquire a fresh token when needed.
56-
result = app.acquire_token_for_client(scopes=config["scope"])
57-
58-
if "access_token" in result:
59-
# Calling graph using the access token
60-
graph_data = requests.get( # Use token to call downstream service
61-
config["endpoint"],
62-
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
63-
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
64-
else:
65-
print(result.get("error"))
66-
print(result.get("error_description"))
67-
print(result.get("correlation_id")) # You may need this when reporting a bug
59+
60+
def acquire_and_use_token():
61+
# Since MSAL 1.23, acquire_token_for_client(...) will automatically look up
62+
# a token from cache, and fall back to acquire a fresh token when needed.
63+
result = global_app.acquire_token_for_client(scopes=config["scope"])
64+
65+
if "access_token" in result:
66+
# Calling graph using the access token
67+
graph_data = requests.get( # Use token to call downstream service
68+
config["endpoint"],
69+
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
70+
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
71+
else:
72+
print("Token acquisition failed") # Examine result["error_description"] etc. to diagnose error
73+
74+
75+
while True: # Here we mimic a long-lived daemon
76+
acquire_and_use_token()
77+
print("Press Ctrl-C to stop.")
78+
time.sleep(5) # Let's say your app would run a workload every X minutes
6879

sample/confidential_client_secret_sample.py

Lines changed: 30 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
3131
import json
3232
import logging
33+
import time
3334

3435
import requests
3536
import msal
@@ -41,28 +42,37 @@
4142

4243
config = json.load(open(sys.argv[1]))
4344

44-
# Create a preferably long-lived app instance which maintains a token cache.
45-
app = msal.ConfidentialClientApplication(
45+
# If for whatever reason you plan to recreate same ClientApplication periodically,
46+
# you shall create one global token cache and reuse it by each ClientApplication
47+
global_token_cache = msal.TokenCache() # The TokenCache() is in-memory.
48+
# See more options in https://msal-python.readthedocs.io/en/latest/#tokencache
49+
50+
# Create a preferably long-lived app instance, to avoid the overhead of app creation
51+
global_app = msal.ConfidentialClientApplication(
4652
config["client_id"], authority=config["authority"],
4753
client_credential=config["secret"],
48-
# token_cache=... # Default cache is in memory only.
49-
# You can learn how to use SerializableTokenCache from
50-
# https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
54+
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
55+
# If absent, ClientApplication will create its own empty token cache
5156
)
5257

53-
# Since MSAL 1.23, acquire_token_for_client(...) will automatically look up
54-
# a token from cache, and fall back to acquire a fresh token when needed.
55-
result = app.acquire_token_for_client(scopes=config["scope"])
56-
57-
if "access_token" in result:
58-
# Calling graph using the access token
59-
graph_data = requests.get( # Use token to call downstream service
60-
config["endpoint"],
61-
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
62-
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
63-
64-
else:
65-
print(result.get("error"))
66-
print(result.get("error_description"))
67-
print(result.get("correlation_id")) # You may need this when reporting a bug
58+
59+
def acquire_and_use_token():
60+
# Since MSAL 1.23, acquire_token_for_client(...) will automatically look up
61+
# a token from cache, and fall back to acquire a fresh token when needed.
62+
result = global_app.acquire_token_for_client(scopes=config["scope"])
63+
64+
if "access_token" in result:
65+
# Calling graph using the access token
66+
graph_data = requests.get( # Use token to call downstream service
67+
config["endpoint"],
68+
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
69+
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
70+
else:
71+
print("Token acquisition failed") # Examine result["error_description"] etc. to diagnose error
72+
73+
74+
while True: # Here we mimic a long-lived daemon
75+
acquire_and_use_token()
76+
print("Press Ctrl-C to stop.")
77+
time.sleep(5) # Let's say your app would run a workload every X minutes
6878

sample/device_flow_sample.py

Lines changed: 65 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
2121
import json
2222
import logging
23+
import time
2324

2425
import requests
2526
import msal
@@ -31,58 +32,70 @@
3132

3233
config = json.load(open(sys.argv[1]))
3334

34-
# Create a preferably long-lived app instance which maintains a token cache.
35-
app = msal.PublicClientApplication(
35+
# If for whatever reason you plan to recreate same ClientApplication periodically,
36+
# you shall create one global token cache and reuse it by each ClientApplication
37+
global_token_cache = msal.TokenCache() # The TokenCache() is in-memory.
38+
# See more options in https://msal-python.readthedocs.io/en/latest/#tokencache
39+
40+
# Create a preferably long-lived app instance, to avoid the overhead of app creation
41+
global_app = msal.PublicClientApplication(
3642
config["client_id"], authority=config["authority"],
37-
# token_cache=... # Default cache is in memory only.
38-
# You can learn how to use SerializableTokenCache from
39-
# https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
43+
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
44+
# If absent, ClientApplication will create its own empty token cache
4045
)
4146

42-
# The pattern to acquire a token looks like this.
43-
result = None
44-
45-
# Note: If your device-flow app does not have any interactive ability, you can
46-
# completely skip the following cache part. But here we demonstrate it anyway.
47-
# We now check the cache to see if we have some end users signed in before.
48-
accounts = app.get_accounts()
49-
if accounts:
50-
logging.info("Account(s) exists in cache, probably with token too. Let's try.")
51-
print("Pick the account you want to use to proceed:")
52-
for a in accounts:
53-
print(a["username"])
54-
# Assuming the end user chose this one
55-
chosen = accounts[0]
56-
# Now let's try to find a token in cache for this account
57-
result = app.acquire_token_silent(config["scope"], account=chosen)
58-
59-
if not result:
60-
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
61-
62-
flow = app.initiate_device_flow(scopes=config["scope"])
63-
if "user_code" not in flow:
64-
raise ValueError(
65-
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))
66-
67-
print(flow["message"])
68-
sys.stdout.flush() # Some terminal needs this to ensure the message is shown
69-
70-
# Ideally you should wait here, in order to save some unnecessary polling
71-
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")
72-
73-
result = app.acquire_token_by_device_flow(flow) # By default it will block
74-
# You can follow this instruction to shorten the block time
75-
# https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow
76-
# or you may even turn off the blocking behavior,
77-
# and then keep calling acquire_token_by_device_flow(flow) in your own customized loop.
78-
79-
if "access_token" in result:
80-
# Calling graph using the access token
81-
graph_data = requests.get( # Use token to call downstream service
82-
config["endpoint"],
83-
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
84-
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
85-
else:
86-
print(result.get("error"))
87-
print(result.get("error_description"))
88-
print(result.get("correlation_id")) # You may need this when reporting a bug
47+
48+
def acquire_and_use_token():
49+
# The pattern to acquire a token looks like this.
50+
result = None
51+
52+
# Note: If your device-flow app does not have any interactive ability, you can
53+
# completely skip the following cache part. But here we demonstrate it anyway.
54+
# We now check the cache to see if we have some end users signed in before.
55+
accounts = global_app.get_accounts()
56+
if accounts:
57+
logging.info("Account(s) exists in cache, probably with token too. Let's try.")
58+
print("Pick the account you want to use to proceed:")
59+
for a in accounts:
60+
print(a["username"])
61+
# Assuming the end user chose this one
62+
chosen = accounts[0]
63+
# Now let's try to find a token in cache for this account
64+
result = global_app.acquire_token_silent(config["scope"], account=chosen)
65+
66+
if not result:
67+
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
68+
69+
flow = global_app.initiate_device_flow(scopes=config["scope"])
70+
if "user_code" not in flow:
71+
raise ValueError(
72+
"Fail to create device flow. Err: %s" % json.dumps(flow, indent=4))
73+
74+
print(flow["message"])
75+
sys.stdout.flush() # Some terminal needs this to ensure the message is shown
76+
77+
# Ideally you should wait here, in order to save some unnecessary polling
78+
# input("Press Enter after signing in from another device to proceed, CTRL+C to abort.")
79+
80+
result = global_app.acquire_token_by_device_flow(flow) # By default it will block
81+
# You can follow this instruction to shorten the block time
82+
# https://msal-python.readthedocs.io/en/latest/#msal.PublicClientApplication.acquire_token_by_device_flow
83+
# or you may even turn off the blocking behavior,
84+
# and then keep calling acquire_token_by_device_flow(flow) in your own customized loop.
85+
86+
if "access_token" in result:
87+
# Calling graph using the access token
88+
graph_data = requests.get( # Use token to call downstream service
89+
config["endpoint"],
90+
headers={'Authorization': 'Bearer ' + result['access_token']},).json()
91+
print("Graph API call result: %s" % json.dumps(graph_data, indent=2))
92+
else:
93+
print("Token acquisition failed") # Examine result["error_description"] etc. to diagnose error
94+
95+
96+
while True: # Here we mimic a long-lived daemon
97+
acquire_and_use_token()
98+
print("Press Ctrl-C to stop.")
99+
time.sleep(5) # Let's say your app would run a workload every X minutes.
100+
# The first acquire_and_use_token() call will prompt. Others hit the cache.
101+

sample/interactive_sample.py

Lines changed: 61 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -20,66 +20,77 @@
2020
2121
python sample.py parameters.json
2222
"""
23-
2423
import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1]
25-
import json, logging, msal, requests
24+
import json, logging, time, msal, requests
2625

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

3130
config = json.load(open(sys.argv[1]))
3231

33-
# Create a preferably long-lived app instance which maintains a token cache.
34-
app = msal.PublicClientApplication(
32+
# If for whatever reason you plan to recreate same ClientApplication periodically,
33+
# you shall create one global token cache and reuse it by each ClientApplication
34+
global_token_cache = msal.TokenCache() # The TokenCache() is in-memory.
35+
# See more options in https://msal-python.readthedocs.io/en/latest/#tokencache
36+
37+
# Create a preferably long-lived app instance, to avoid the overhead of app creation
38+
global_app = msal.PublicClientApplication(
3539
config["client_id"], authority=config["authority"],
3640
#allow_broker=True, # If opted in, you will be guided to meet the prerequisites, when applicable
3741
# See also: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-desktop-acquire-token-wam#wam-value-proposition
38-
# token_cache=... # Default cache is in memory only.
39-
# You can learn how to use SerializableTokenCache from
40-
# https://msal-python.readthedocs.io/en/latest/#msal.SerializableTokenCache
42+
token_cache=global_token_cache, # Let this app (re)use an existing token cache.
43+
# If absent, ClientApplication will create its own empty token cache
4144
)
4245

43-
# The pattern to acquire a token looks like this.
44-
result = None
45-
46-
# Firstly, check the cache to see if this end user has signed in before
47-
accounts = app.get_accounts(username=config.get("username"))
48-
if accounts:
49-
logging.info("Account(s) exists in cache, probably with token too. Let's try.")
50-
print("Account(s) already signed in:")
51-
for a in accounts:
52-
print(a["username"])
53-
chosen = accounts[0] # Assuming the end user chose this one to proceed
54-
print("Proceed with account: %s" % chosen["username"])
55-
# Now let's try to find a token in cache for this account
56-
result = app.acquire_token_silent(config["scope"], account=chosen)
57-
58-
if not result:
59-
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
60-
print("A local browser window will be open for you to sign in. CTRL+C to cancel.")
61-
result = app.acquire_token_interactive( # Only works if your app is registered with redirect_uri as http://localhost
62-
config["scope"],
63-
#parent_window_handle=..., # If broker is enabled, you will be guided to provide a window handle
64-
login_hint=config.get("username"), # Optional.
65-
# If you know the username ahead of time, this parameter can pre-fill
66-
# the username (or email address) field of the sign-in page for the user,
67-
# Often, apps use this parameter during reauthentication,
68-
# after already extracting the username from an earlier sign-in
69-
# by using the preferred_username claim from returned id_token_claims.
70-
71-
#prompt=msal.Prompt.SELECT_ACCOUNT, # Or simply "select_account". Optional. It forces to show account selector page
72-
#prompt=msal.Prompt.CREATE, # Or simply "create". Optional. It brings user to a self-service sign-up flow.
73-
# Prerequisite: https://docs.microsoft.com/en-us/azure/active-directory/external-identities/self-service-sign-up-user-flow
74-
)
75-
76-
if "access_token" in result:
77-
# Calling graph using the access token
78-
graph_response = requests.get( # Use token to call downstream service
79-
config["endpoint"],
80-
headers={'Authorization': 'Bearer ' + result['access_token']},)
81-
print("Graph API call result: %s ..." % graph_response.text[:100])
82-
else:
83-
print(result.get("error"))
84-
print(result.get("error_description"))
85-
print(result.get("correlation_id")) # You may need this when reporting a bug
46+
47+
def acquire_and_use_token():
48+
# The pattern to acquire a token looks like this.
49+
result = None
50+
51+
# Firstly, check the cache to see if this end user has signed in before
52+
accounts = global_app.get_accounts(username=config.get("username"))
53+
if accounts:
54+
logging.info("Account(s) exists in cache, probably with token too. Let's try.")
55+
print("Account(s) already signed in:")
56+
for a in accounts:
57+
print(a["username"])
58+
chosen = accounts[0] # Assuming the end user chose this one to proceed
59+
print("Proceed with account: %s" % chosen["username"])
60+
# Now let's try to find a token in cache for this account
61+
result = global_app.acquire_token_silent(config["scope"], account=chosen)
62+
63+
if not result:
64+
logging.info("No suitable token exists in cache. Let's get a new one from AAD.")
65+
print("A local browser window will be open for you to sign in. CTRL+C to cancel.")
66+
result = global_app.acquire_token_interactive( # Only works if your app is registered with redirect_uri as http://localhost
67+
config["scope"],
68+
#parent_window_handle=..., # If broker is enabled, you will be guided to provide a window handle
69+
login_hint=config.get("username"), # Optional.
70+
# If you know the username ahead of time, this parameter can pre-fill
71+
# the username (or email address) field of the sign-in page for the user,
72+
# Often, apps use this parameter during reauthentication,
73+
# after already extracting the username from an earlier sign-in
74+
# by using the preferred_username claim from returned id_token_claims.
75+
76+
#prompt=msal.Prompt.SELECT_ACCOUNT, # Or simply "select_account". Optional. It forces to show account selector page
77+
#prompt=msal.Prompt.CREATE, # Or simply "create". Optional. It brings user to a self-service sign-up flow.
78+
# Prerequisite: https://docs.microsoft.com/en-us/azure/active-directory/external-identities/self-service-sign-up-user-flow
79+
)
80+
81+
if "access_token" in result:
82+
# Calling graph using the access token
83+
graph_response = requests.get( # Use token to call downstream service
84+
config["endpoint"],
85+
headers={'Authorization': 'Bearer ' + result['access_token']},)
86+
print("Graph API call result: %s ..." % graph_response.text[:100])
87+
else:
88+
print("Token acquisition failed") # Examine result["error_description"] etc. to diagnose error
89+
90+
91+
while True: # Here we mimic a long-lived daemon
92+
acquire_and_use_token()
93+
print("Press Ctrl-C to stop.")
94+
time.sleep(5) # Let's say your app would run a workload every X minutes
95+
# The first acquire_and_use_token() call will prompt. Others hit the cache.
96+

0 commit comments

Comments
 (0)