Getting Tokens with Azure CLI
By Anatoly Mironov
If you need to call Microsoft Graph or some custom APIs from your computer, you might use Azure CLI.
Use cases can be Python Notebooks, or automation shell scripts, console applications and more.
Login to Azure CLI
First you need to log in to Azure CLI:
az login
In case you don’t have Azure CLI and you happen to use a Windows machine, my best advice is to get it using winget
:
winget install -e --id Microsoft.AzureCLI
Azure CLI Service Principal
Azure CLI is not visible in the Entra ID admin portal. Its app id is: 04b07795-8ddb-461a-bbee-02f9e1bf7b46
, this is needed when you will generate access tokens to your custom APIs, more on that later.
DefaultAzureCredential
DefaultAzureCredential is pretty smart, read this to understand it better:
Once logged in using Azure CLI in your terminal you can use the credential right away, without messing around with secrets, passwords or any of this. With that credential you can get a token to Graph as well.
In Python, the minimal path to awesome is:
from azure.identity import DefaultAzureCredential
credential = DefaultAzureCredential
token = credential.get_token('https://graph.microsoft.com/.default')
The package you need is azure-identity
.
Graph but not all Graph
Azure CLI as a Microsoft first-party Service Principal has permissions to Graph, but not all permissions.
In my case I was able to get Site information, download files, get list items. But I could NOT get a list of document libraries (requires at least Sites.Read.All). So there are limitations, but there are many use cases where it is enough.
Graph Explorer Token scp: Calendars.ReadWrite Contacts.ReadWrite DeviceManagementApps.Read.All DeviceManagementApps.ReadWrite.All DeviceManagementConfiguration.Read.All DeviceManagementConfiguration.ReadWrite.All DeviceManagementManagedDevices.PrivilegedOperations.All DeviceManagementManagedDevices.Read.All DeviceManagementManagedDevices.ReadWrite.All DeviceManagementRBAC.Read.All DeviceManagementRBAC.ReadWrite.All DeviceManagementServiceConfig.Read.All DeviceManagementServiceConfig.ReadWrite.All Directory.AccessAsUser.All Directory.ReadWrite.All Files.ReadWrite.All Group.ReadWrite.All IdentityRiskEvent.Read.All Mail.ReadWrite MailboxSettings.ReadWrite Notes.ReadWrite.All openid People.Read profile Reports.Read.All Sites.ReadWrite.All Tasks.ReadWrite User.Read User.ReadBasic.All User.ReadWrite User.ReadWrite.All email
Azure CLI Token scp: AuditLog.Read.All Directory.AccessAsUser.All email Group.ReadWrite.All openid profile User.ReadWrite.All
Custom APIs
If you have exposed a custom API and you want call it on behalf of your user. While it is common scenario in a frontend/backend setup, it’s a bit of a hazzle if you want to automate something in Python Notebooks or scripts. Here comes in Azure CLI.
If you add the Azure CLI to the consumers of your App Registration in Entra ID, you will be able to issue a valid Bearer token which you then can use to authenticate your api calls.
I have not found official documentation on that, so it might be unsupported, and you should not rely on that in production scenarios.
Refreshing tokens
One nice thing with Azure CLI and DefaultAzureCredential is that it is super easy to get a new token when it is about to expire.
from datetime import datetime, timezone
# Get the current time as a Unix timestamp
current_time = datetime.now(timezone.utc).timestamp()
# Calculate the time left in seconds
time_left_seconds = token.expires_on - current_time
time_left_seconds
if (time_left_seconds < 300):
print("Token is about to expire in less than 5 minutes")
token = credential.get_token(custom_scope)
else:
print("Token is still valid")
More tokens
Azure CLI and DefaultAzureCredential can do even more, you can authenticate to Azure DevOps API.
Here is how you can call Azure DevOps with a token created by Azure CLI.
token = credential.get_token("499b84ac-1321-427f-aa17-267ca6975798/.default")
base_url = "https://dev.azure.com/<YOUR_ORG>/" \
# call the API using the token to list all the projects
url = f"{base_url}/_apis/projects?api-version=7.1-preview.1"
response = requests.get(
url, headers={"Authorization": f"Bearer {token.token}"}
)
response.raise_for_status()
response.json()