Skip to content

How to authenticate to Microsoft products programmatically from your laptop

List of Microsoft products covered in this document:
- Azure
- Azure DevOps
- Microsoft Graph (for Intune)

Note: We will use Python as our programming language.

Table of Contents


1. Prerequisites


2. How does Azure CLI authentication work

Starting in version 2.30.0, Azure CLI uses Microsoft Authentication Library (MSAL) as the underlying authentication library. MSAL uses Azure Active Directory v2.0 authentication flow to provide more functionality and increases security for token cache.

What does it mean?
When you sign in with a user account via az login, Azure CLI generates a cache token using Microsoft Authentication Library (MSAL): msal_token_cache.json. This file is located by default at:
- $HOME/.azure on Linux and macOS,
- %USERPROFILE%\.azure on Windows

Note: The MSAL token cache and service principal entries are saved as encrypted files on Windows, and plaintext files on Linux and macOS.

msal_token_cache.json contains the following tokens and data:
- AccessToken: An access token is a security token issued by an authorization server as part of an OAuth 2.0 flow. It contains information about the user and the resource for which the token is intended. The information can be used to access web APIs and other protected resources. Resources validate access tokens to grant access to a client application.

Azure CLI will generate two access tokens:
- one token with general scope
- one token with tenant scope
<details>
<summary>Tokens details</summary>

```json
{   
    "credential_type": "AccessToken",
    "secret": "<Secret>",
    "home_account_id": "<User Object ID>.<Tenant ID>",
    "environment": "login.microsoftonline.com",
    "client_id": "04b07795-8ddb-461a-bbee-02f9e1bf7b46",/* Microsoft Azure CLI */
    "target": "https://management.core.windows.net//.default https://management.core.windows.net//user_impersonation",
    "realm": "organizations", /* General scope */
    "token_type": "Bearer",
    "local_account_id": "<User Object ID>",
    "cached_at": "<time>",
    "expires_on": "<time>",
    "extended_expires_on": "<time>"

},
{
    ...
    "realm": "<Tenant ID>", /* Tenant scope */
    ...
}
```
</details> <br>
  • Account: information about the user. Some information overlaps with AccessToken to allow fetching/renewing a token without having to read an existing access token. Azure CLI will generate one Account.

    Account details

    {
        "home_account_id": "<User Object ID>.<Tenant ID>",
        "environment": "login.microsoftonline.com",
        "realm": "organizations", /* General scope */
        "local_account_id": "<User Object ID>",
        "username": "<User Email>",
        "authority_type": "MSSTS", /* Microsoft Security Token Service */
        "account_source": "authorization_code" /* Authorization Code Flow */
    }
    


  • IdToken (Not used in our usecase): ID tokens are a type of security token that serves as proof of authentication, confirming that a user is successfully authenticated. Information in ID tokens enables the client to verify that a user is who they claim to be. ID tokens differ from access tokens, which serve as proof of authorization. Confidential clients should validate ID tokens. You shouldn't use an ID token to call an API.

    Azure CLI will generate two ID tokens:
    - one token with general scope
    - one token with tenant scope


    Tokens details

    {   
        "credential_type": "IdToken",
        "secret": "<Secret>",
        "home_account_id": "<User Object ID>.<Tenant ID>",
        "environment": "login.microsoftonline.com",
        "realm": "organizations", /* General scope */
        "client_id": "04b07795-8ddb-461a-bbee-02f9e1bf7b46" /* Microsoft Azure CLI */
    },
    {
        ...
        "realm": "<Tenant ID>", /* Tenant scope */
        ...
    }
    


  • RefreshToken: As access tokens are valid for only a short period of time, Azure CLI will generate one refresh token to let the client get new access tokens without having to log in again. By default, this token is valid for 24 hours.

    Tokens details

    {   
        "credential_type": "RefreshToken",
        "secret": "<Secret>",
        "home_account_id": "<User Object ID>.<Tenant ID>",
        "environment": "login.microsoftonline.com",
        "client_id": "04b07795-8ddb-461a-bbee-02f9e1bf7b46", /* Microsoft Azure CLI */
        "target": "https://management.core.windows.net//.default https://management.core.windows.net//user_impersonation",
        "last_modification_time": "<Date>",
        "family_id": "1"
    
    }
    



3. How to authenticate in your script using your Azure CLI tokens

Before starting, it's important to understand that the existing access tokens created by Azure CLI cannot be used for the following use cases because they are set with the target (scope): management.core.windows.net. This scope is for the older Azure endpoint, sometimes referred to as the Azure Service Management (ASM) or "classic" deployment model endpoint.

Our script will use the refresh token and account information to request the correct access token without having to sign in again.

To begin, import the DefaultAzureCredential class in your script. This class provides an authentication flow that will try every authentication method in a specific order until it finds a valid one. In our case, it will use AzureCliCredential. You can then use the get_token method to request an access token.

##  Third party imports
from azure.identity import DefaultAzureCredential

##  Create a DefaultAzureCredential instance
credential = DefaultAzureCredential()

##  Request a new token by providing the correct scope
token = credential.get_token(<SCOPE>)

You will find which scope to use in the following sections. After fetching an access token, it will be available in the cache file msal_token_cache.json.


3.1 Request an Azure access token

To authenticate to Azure, we will use the newer, more modern Azure Resource Manager (ARM) endpoint. This endpoint supports the latest features and services in Azure.

Scope: https://management.azure.com/.default

Note: .default is equivalent to user_impersonation. You will request the current permissions associated with your user.


3.2 Request an Azure DevOps access token

Scope: 499b84ac-1321-427f-aa17-267ca6975798/.default

Note 1: .default is equivalent to user_impersonation. You will request the current permissions associated with your user.

Note 2: 499b84ac-1321-427f-aa17-267ca6975798 is the official endpoint for Azure DevOps API.


3.3 Request a Microsoft Graph (Intune) access token

You can use the Intune API in Microsoft Graph to access Intune device and application information, manage devices, manage apps, and automate Intune. More information can be found in the official documentation.

Scope: https://graph.microsoft.com/.default

Unlike Azure and Azure DevOps, the .default scope for Microsoft Graph will only give a hardcoded list of permissions:
- AuditLog.Read.All
- Directory.AccessAsUser.All
- Group.ReadWrite.All
- User.ReadWrite.All

The reason is that we cannot grant permissions directly to an Azure user. You need to go through an Azure application and grant this application delegated permissions so the user can assume them via the application.

You can request specific access via your configured Azure application in this way:

from azure.identity import InteractiveBrowserCredential
from msgraph import GraphServiceClient

SCOPES = ["DeviceManagementManagedDevices.Read.All"]
CLIENT_ID = "your_client_id"
TENANT_ID = "your_tenant_id"

intune_app_credentials = InteractiveBrowserCredential(client_id=CLIENT_ID, tenant_id=TENANT_ID)
graph_client = GraphServiceClient(credentials=intune_app_credentials, scopes=SCOPES)

Resources