Skip to content

Microsoft Graph Authentication

Authentication flows for the Graph client — pick the one that matches who your app runs as and where it runs.


Which flow should I use?

flowchart TD
    A[Who will sign in?] --> B[An application / daemon]
    A --> C[A user]

    B --> D{Where will it run?}
    D -->|Production| E[Client certificate
more secure]
    D -->|Development / simple| F[Client secret
simplest setup]

    C --> G{User present to interact?}
    G -->|Yes| H[Interactive auth
supports MFA, SSO]
    G -->|No| I{Has a browser?}
    I -->|Yes, visit URL| N[Device code flow
headless CLI, SSH,
remote server]
    I -->|No browser at all| O["ROPC (password grant)
no MFA, legacy"]

    E --> J[with_client_cert.py]
    F --> K[with_client_secret.py]
    H --> L[interactive.py]
    N --> P[with_device_flow.py]
    O --> M[with_user_creds.py]

    style A fill:#1a73e8,color:#fff
    style B fill:#e8f0fe
    style C fill:#e8f0fe
    style E fill:#fff3cd
    style F fill:#fff3cd
    style H fill:#d4edda
    style I fill:#f8d7da

App-only (application permissions)

Client secret

App-only access for daemons, cron jobs, and CI/CD — simplest setup, no user involved.

client = GraphClient(tenant="contoso.onmicrosoft.com").with_client_secret(
    client_id="<client_id>", client_secret="<client_secret>"
)
site = client.sites.root.get().execute_query()
print(site.web_url)

Client certificate

App-only access for production daemons — X.509 certificate instead of a shared secret.

app = msal.ConfidentialClientApplication(
    client_id,
    authority="https://login.microsoftonline.com/contoso.onmicrosoft.com",
    client_credential={"thumbprint": cert_thumbprint, "private_key": open("cert.pem").read()},
)
token = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])

client = GraphClient(token)
for drive in client.drives.get().top(10).execute_query():
    print(drive.web_url)

User sign-in (delegated permissions)

Interactive

User sign-in with a browser prompt — supports MFA, SSO, and consent.

client = GraphClient(tenant="contoso.onmicrosoft.com").with_token_interactive(client_id="<client_id>")
me = client.me.get().execute_query()
print(f"Welcome, {me.given_name}!")

Device code flow

Headless CLI, SSH, and remote servers — the user visits a URL on another device.

client = GraphClient(tenant="contoso.onmicrosoft.com").with_device_flow(client_id="<client_id>")
me = client.me.get().execute_query()
print(f"Authenticated as: {me.user_principal_name}")

Username & password (ROPC)

User context without interactivity — no MFA, legacy flow (Resource Owner Password Credentials).

client = GraphClient(tenant="contoso.onmicrosoft.com").with_username_and_password(
    client_id="<client_id>", username="<user>", password="<password>"
)
me = client.me.get().execute_query()
print(me)

Custom token callback

Bring your own token acquisition — secrets vault, managed identity, or a custom identity provider.

def acquire_token() -> dict:
    app = msal.ConfidentialClientApplication(
        client_id, client_credential=client_secret,
        authority="https://login.microsoftonline.com/contoso.onmicrosoft.com",
    )
    result = app.acquire_token_for_client(scopes=["https://graph.microsoft.com/.default"])
    if not result or "access_token" not in result:
        raise RuntimeError(f"Token acquisition failed: {result}")
    return result

client = GraphClient(tenant="contoso.onmicrosoft.com", token_callback=acquire_token)
org = client.organization.get().execute_query()

Special environments

Microsoft Entra External ID (CIAM)

Customer identity / External ID tenants — connect via the ciamlogin.com authority.

authority = "https://contoso.ciamlogin.com"
client = GraphClient(tenant="contoso.onmicrosoft.com", authority=authority).with_client_secret(
    client_id="<client_id>", client_secret="<client_secret>"
)
org = client.organization.get().execute_query()

National clouds

Sovereign clouds (GCC High, DoD, China) via AzureEnvironment — applies to any flow above.

from office365.azure_env import AzureEnvironment

client = GraphClient(
    tenant="contoso.onmicrosoft.com", environment=AzureEnvironment.USGovernmentHigh
).with_client_secret(client_id="<client_id>", client_secret="<client_secret>")
org = client.organization.get().execute_query()

National cloud environments

Environment AzureEnvironment
Global Global
US Government GCC USGovernment
US Government GCC High USGovernmentHigh
US Government DoD USGovernmentDoD
China China
Germany (legacy) Germany

Best practice: verify permissions upfront

Guard against missing permissions or licenses before making a call:

client = (
    GraphClient(tenant="contoso.onmicrosoft.com")
    .with_client_secret(client_id="<client_id>", client_secret="<client_secret>")
    .require_application_permission("DeviceManagementConfiguration.Read.All")
    .require_delegated_permission("User.Read", "User.ReadWrite.All")
    .require_license("DEVELOPERPACK_E5")
)

Official docs