-
Notifications
You must be signed in to change notification settings - Fork 206
Add support for acquiring a token with a pre-signed JWT #271
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
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
43d4711
Add support for acquiring a token with a client provided, pre-signed
lochiiconnectivity 8aa8b96
Changes to parameter name for #271
8c0774a
Address comment in #271 "No need to repeat this statement twice in bo…
9b98f80
merge rayluo / microsoft-authentication-library-for-python:patch1
4fb5cee
Update msal/application.py
lochiiconnectivity a621c86
Update tests/test_e2e.py
lochiiconnectivity 5272901
Resolve merge conflict
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
""" | ||
The configuration file would look like this (sans those // comments): | ||
{ | ||
"tenant": "your_tenant_name", | ||
// Your target tenant, DNS name | ||
"client_id": "your_client_id", | ||
// Target app ID in Azure AD | ||
"scope": ["https://graph.microsoft.com/.default"], | ||
// Specific to Client Credentials Grant i.e. acquire_token_for_client(), | ||
// you don't specify, in the code, the individual scopes you want to access. | ||
// Instead, you statically declared them when registering your application. | ||
// Therefore the only possible scope is "resource/.default" | ||
// (here "https://graph.microsoft.com/.default") | ||
// which means "the static permissions defined in the application". | ||
"vault_tenant": "your_vault_tenant_name", | ||
// Your Vault tenant may be different to your target tenant | ||
// If that's not the case, you can set this to the same | ||
// as "tenant" | ||
"vault_clientid": "your_vault_client_id", | ||
// Client ID of your vault app in your vault tenant | ||
"vault_clientsecret": "your_vault_client_secret", | ||
// Secret for your vault app | ||
"vault_url": "your_vault_url", | ||
// URL of your vault app | ||
"cert": "your_cert_name", | ||
// Name of your certificate in your vault | ||
"cert_thumb": "your_cert_thumbprint", | ||
// Thumbprint of your certificate | ||
"endpoint": "https://graph.microsoft.com/v1.0/users" | ||
// For this resource to work, you need to visit Application Permissions | ||
// page in portal, declare scope User.Read.All, which needs admin consent | ||
// https://github.com/Azure-Samples/ms-identity-python-daemon/blob/master/2-Call-MsGraph-WithCertificate/README.md | ||
} | ||
You can then run this sample with a JSON configuration file: | ||
python sample.py parameters.json | ||
""" | ||
|
||
import base64 | ||
import json | ||
import logging | ||
import requests | ||
import sys | ||
import time | ||
import uuid | ||
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 | ||
|
||
from azure.keyvault import KeyVaultClient, KeyVaultAuthentication | ||
from azure.common.credentials import ServicePrincipalCredentials | ||
from cryptography.hazmat.backends import default_backend | ||
from cryptography.hazmat.primitives import hashes | ||
|
||
config = json.load(open(sys.argv[1])) | ||
|
||
def auth_vault_callback(server, resource, scope): | ||
credentials = ServicePrincipalCredentials( | ||
client_id=config['vault_clientid'], | ||
secret=config['vault_clientsecret'], | ||
tenant=config['vault_tenant'], | ||
resource='https://vault.azure.net' | ||
) | ||
token = credentials.token | ||
return token['token_type'], token['access_token'] | ||
|
||
|
||
def make_vault_jwt(): | ||
|
||
header = { | ||
'alg': 'RS256', | ||
'typ': 'JWT', | ||
'x5t': base64.b64encode( | ||
config['cert_thumb'].decode('hex')) | ||
} | ||
header_b64 = base64.b64encode(json.dumps(header).encode('utf-8')) | ||
|
||
body = { | ||
'aud': "https://login.microsoftonline.com/%s/oauth2/token" % | ||
config['tenant'], | ||
'exp': (int(time.time()) + 600), | ||
'iss': config['client_id'], | ||
'jti': str(uuid.uuid4()), | ||
'nbf': int(time.time()), | ||
'sub': config['client_id'] | ||
} | ||
body_b64 = base64.b64encode(json.dumps(body).encode('utf-8')) | ||
|
||
full_b64 = b'.'.join([header_b64, body_b64]) | ||
|
||
client = KeyVaultClient(KeyVaultAuthentication(auth_vault_callback)) | ||
chosen_hash = hashes.SHA256() | ||
hasher = hashes.Hash(chosen_hash, default_backend()) | ||
hasher.update(full_b64) | ||
digest = hasher.finalize() | ||
signed_digest = client.sign(config['vault_url'], | ||
config['cert'], '', 'RS256', | ||
digest).result | ||
|
||
full_token = b'.'.join([full_b64, base64.b64encode(signed_digest)]) | ||
|
||
return full_token | ||
|
||
|
||
authority = "https://login.microsoftonline.com/%s" % config['tenant'] | ||
|
||
app = msal.ConfidentialClientApplication( | ||
config['client_id'], authority=authority, client_credential={"client_assertion": make_vault_jwt()} | ||
) | ||
|
||
# The pattern to acquire a token looks like this. | ||
result = None | ||
|
||
# Firstly, looks up a token from cache | ||
# Since we are looking for token for the current app, NOT for an end user, | ||
# notice we give account parameter as None. | ||
result = app.acquire_token_silent(config["scope"], account=None) | ||
|
||
if not result: | ||
logging.info("No suitable token exists in cache. Let's get a new one from AAD.") | ||
result = app.acquire_token_for_client(scopes=config["scope"]) | ||
|
||
if "access_token" in result: | ||
# Calling graph using the access token | ||
graph_data = requests.get( # Use token to call downstream service | ||
config["endpoint"], | ||
headers={'Authorization': 'Bearer ' + result['access_token']},).json() | ||
print("Graph API call result: %s" % json.dumps(graph_data, indent=2)) | ||
else: | ||
print(result.get("error")) | ||
print(result.get("error_description")) | ||
print(result.get("correlation_id")) # You may need this when reporting a bug | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.