|
1 | 1 | """ |
2 | | -The configuration file would look like this (sans those // comments): |
3 | | -
|
4 | | -{ |
5 | | - "authority": "https://login.microsoftonline.com/Enter_the_Tenant_Name_Here", |
6 | | - "client_id": "your_client_id came from https://learn.microsoft.com/entra/identity-platform/quickstart-register-app", |
7 | | - "scope": ["https://graph.microsoft.com/.default"], |
8 | | - // Specific to Client Credentials Grant i.e. acquire_token_for_client(), |
9 | | - // you don't specify, in the code, the individual scopes you want to access. |
10 | | - // Instead, you statically declared them when registering your application. |
11 | | - // Therefore the only possible scope is "resource/.default" |
12 | | - // (here "https://graph.microsoft.com/.default") |
13 | | - // which means "the static permissions defined in the application". |
14 | | -
|
15 | | - "secret": "The secret generated by AAD during your confidential app registration", |
16 | | - // For information about generating client secret, refer: |
17 | | - // https://github.com/AzureAD/microsoft-authentication-library-for-python/wiki/Client-Credentials#registering-client-secrets-using-the-application-registration-portal |
18 | | -
|
19 | | - "endpoint": "https://graph.microsoft.com/v1.0/users" |
20 | | - // For this resource to work, you need to visit Application Permissions |
21 | | - // page in portal, declare scope User.Read.All, which needs admin consent |
22 | | - // https://github.com/Azure-Samples/ms-identity-python-daemon/blob/master/1-Call-MsGraph-WithSecret/README.md |
23 | | -} |
24 | | -
|
25 | | -You can then run this sample with a JSON configuration file: |
26 | | -
|
27 | | - python sample.py parameters.json |
| 2 | +This sample demonstrates a daemon application that acquires a token using a |
| 3 | +client secret and then calls a web API with the token. |
| 4 | +
|
| 5 | +This sample loads its configuration from a .env file. |
| 6 | +
|
| 7 | +To make this sample work, you need to choose one of the following templates: |
| 8 | +
|
| 9 | + .env.sample.entra-id |
| 10 | + .env.sample.external-id |
| 11 | + .env.sample.external-id-with-custom-domain |
| 12 | +
|
| 13 | +Copy the chosen template to a new file named .env, and fill in the values. |
| 14 | +
|
| 15 | +You can then run this sample: |
| 16 | +
|
| 17 | + python name_of_this_script.py |
28 | 18 | """ |
29 | 19 |
|
30 | | -import sys # For simplicity, we'll read config file from 1st CLI param sys.argv[1] |
31 | 20 | import json |
32 | 21 | import logging |
| 22 | +import os |
33 | 23 | import time |
34 | 24 |
|
35 | | -import requests |
| 25 | +from dotenv import load_dotenv # Need "pip install python-dotenv" |
36 | 26 | import msal |
| 27 | +import requests |
37 | 28 |
|
38 | 29 |
|
39 | 30 | # Optional logging |
40 | 31 | # logging.basicConfig(level=logging.DEBUG) # Enable DEBUG log for entire script |
41 | 32 | # logging.getLogger("msal").setLevel(logging.INFO) # Optionally disable MSAL DEBUG logs |
42 | 33 |
|
43 | | -config = json.load(open(sys.argv[1])) |
| 34 | +load_dotenv() # We use this to load configuration from a .env file |
44 | 35 |
|
45 | 36 | # If for whatever reason you plan to recreate same ClientApplication periodically, |
46 | 37 | # you shall create one global token cache and reuse it by each ClientApplication |
|
49 | 40 |
|
50 | 41 | # Create a preferably long-lived app instance, to avoid the overhead of app creation |
51 | 42 | global_app = msal.ConfidentialClientApplication( |
52 | | - config["client_id"], authority=config["authority"], |
53 | | - client_credential=config["secret"], |
| 43 | + os.getenv('CLIENT_ID'), |
| 44 | + authority=os.getenv('AUTHORITY'), # For Entra ID or External ID |
| 45 | + oidc_authority=os.getenv('OIDC_AUTHORITY'), # For External ID with custom domain |
| 46 | + client_credential=os.getenv('CLIENT_SECRET'), |
54 | 47 | token_cache=global_token_cache, # Let this app (re)use an existing token cache. |
55 | 48 | # If absent, ClientApplication will create its own empty token cache |
56 | 49 | ) |
| 50 | +scopes = os.getenv("SCOPE", "").split() |
57 | 51 |
|
58 | 52 |
|
59 | 53 | def acquire_and_use_token(): |
60 | 54 | # Since MSAL 1.23, acquire_token_for_client(...) will automatically look up |
61 | 55 | # 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"]) |
| 56 | + result = global_app.acquire_token_for_client(scopes=scopes) |
63 | 57 |
|
64 | 58 | if "access_token" in result: |
65 | 59 | print("Token was obtained from:", result["token_source"]) # Since MSAL 1.25 |
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)) |
| 60 | + if os.getenv('ENDPOINT'): |
| 61 | + # Calling a web API using the access token |
| 62 | + api_result = requests.get( |
| 63 | + os.getenv('ENDPOINT'), |
| 64 | + headers={'Authorization': 'Bearer ' + result['access_token']}, |
| 65 | + ).json() # Assuming the response is JSON |
| 66 | + print("Web API call result", json.dumps(api_result, indent=2)) |
| 67 | + else: |
| 68 | + print("Token acquisition result", json.dumps(result, indent=2)) |
71 | 69 | else: |
72 | 70 | print("Token acquisition failed", result) # Examine result["error_description"] etc. to diagnose error |
73 | 71 |
|
|
0 commit comments