|
20 | 20 |
|
21 | 21 | python sample.py parameters.json
|
22 | 22 | """
|
23 |
| - |
24 | 23 | 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 |
26 | 25 |
|
27 | 26 | # Optional logging
|
28 | 27 | # logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script
|
29 | 28 | # logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs
|
30 | 29 |
|
31 | 30 | config = json.load(open(sys.argv[1]))
|
32 | 31 |
|
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( |
35 | 39 | config["client_id"], authority=config["authority"],
|
36 | 40 | #allow_broker=True, # If opted in, you will be guided to meet the prerequisites, when applicable
|
37 | 41 | # 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 |
41 | 44 | )
|
42 | 45 |
|
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