Microsoft 365

How to Create an Entra App Registration for Microsoft Graph API Calls

In this post
  1. Overview
  2. Prerequisites
  3. Step 1: Register the application
  4. Step 2: Record the identifiers
  5. Step 3: Configure credentials
  6. Step 4: Add Graph permissions and grant admin consent
  7. Step 5: Get a token and make a test call
    1. curl
    2. PowerShell (Microsoft Graph PowerShell SDK)
    3. Python (msal + requests)
  8. Troubleshooting
  9. Security best practices and maintenance
  10. References
An Entra app registration gives your code an identity that Microsoft Graph accepts: a client ID, a way to prove it (secret, certificate or federated credential), and a set of Graph permissions. This guide covers the app-only path (client credentials flow), which suits scripts, services and automation that run without a signed-in user.

Overview

Pick the permission type first, because it decides the rest of the setup:
Application permissions Delegated permissions
Runs as The app itself The signed-in user, limited by the app’s scopes
Typical use Daemons, scheduled jobs, automation Web, mobile and desktop apps with sign-in
Consent Admin consent always required User or admin consent
Token flow Client credentials Authorization code with PKCE
Reach Tenant-wide for each granted permission Only what the user can access
Steps 1 to 5 use application permissions. Where delegated access differs, the step says so.

Prerequisites

    • Permission to register apps. By default any member user can, unless your tenant turned off “Users can register applications”. If it is off, you need the Application Developer, Cloud Application Administrator or Application Administrator role.
    • Someone who can grant admin consent. For Microsoft Graph application permissions that means Privileged Role Administrator or Global Administrator. Application Administrator and Cloud Application Administrator can consent to other APIs but not to Graph app roles.
    • A secure place to keep credentials, such as Azure Key Vault or your CI system’s secret store.

Step 1: Register the application

Registering creates the app object in your tenant and establishes a one-way trust: your app trusts the Microsoft identity platform.
    1. Sign in to the Microsoft Entra admin center.
    1. Go to Entra ID > App registrations and select New registration.
    1. Enter a Name. Users may see it on consent screens, so make it descriptive, for example Contoso Graph Reporting Job.
    1. Under Supported account types, choose:
        • Accounts in this organizational directory only (single tenant) for an internal app or automation. This is the right choice for most Graph scripts.
        • Accounts in any organizational directory (multitenant) only if other tenants will use the app.
    1. Leave Redirect URI blank for app-only access. For delegated access, pick the platform (Web, SPA or Public client/native) and enter the URI your app receives the sign-in response on.
    1. Select Register. The portal opens the app’s Overview pane.

Step 2: Record the identifiers

Copy these two values from the Overview pane; every token request needs them.
Portal label Used as Example
Application (client) ID client_id 11111111-2222-3333-4444-555555555555
Directory (tenant) ID The {tenant} segment of the token URL aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee
Neither value is secret, but treat them as configuration, not as credentials.

Step 3: Configure credentials

The app needs a credential to prove its identity when it requests a token. Open Certificates & secrets in the app registration and choose one:
Option Best for Trade-off
Federated credential Workloads on GitHub Actions, Azure, Kubernetes or other trusted identity providers No secret to store or rotate; needs a supported issuer
Certificate Production services and long-lived jobs Stronger than a secret; you manage the key pair and renewal
Client secret Quick tests and local development Easiest to set up; a plain string that leaks easily and expires
Use a federated credential or certificate for anything that outlives a test. If you use a client secret:
    1. Select Certificates & secrets > Client secrets > New client secret.
    1. Add a description and an expiry. Keep the lifetime short, such as 6 months.
    1. Select Add, then copy the Value immediately. The portal never shows it again.
    1. Store it in Key Vault or your secret manager. Never commit it to source control.
For a certificate, upload the public key (.cer, .pem or .crt) and keep the private key in your secret store. A token only carries the permissions you add here and an admin approves.
    1. In the app registration, open API permissions > Add a permission > Microsoft Graph.
    1. Choose Application permissions for app-only access (or Delegated permissions for a signed-in user).
    1. Search for the permission your API needs, for example User.Read.All, tick it and select Add permissions.
    1. Select Grant admin consent for <your tenant> and confirm. The Status column should show a green check.
Apply least privilege:
    • Pick the narrowest permission that works. Prefer .Read over .ReadWrite, and a specific scope such as Mail.ReadBasic.All over a broad one.
    • Avoid .All permissions where a resource-specific alternative exists, such as resource-specific consent for Teams or SharePoint (Sites.Selected).
    • Remove the default User.Read delegated permission if the app is app-only and does not need it.
    • Re-check consent after any permission change; new permissions are not active until an admin consents.

Step 5: Get a token and make a test call

The client credentials flow posts your credentials to the tenant’s token endpoint and receives a bearer token. Set scope to https://graph.microsoft.com/.default, which requests every application permission already consented for the app. Use User.Read.All as the example permission; the test call below lists users.

curl

TENANT_ID="<tenant-id>"
CLIENT_ID="<client-id>"
CLIENT_SECRET="<client-secret>"

TOKEN=$(curl -s -X POST \
  "https://login.microsoftonline.com/$TENANT_ID/oauth2/v2.0/token" \
  -d "client_id=$CLIENT_ID" \
  -d "client_secret=$CLIENT_SECRET" \
  -d "scope=https://graph.microsoft.com/.default" \
  -d "grant_type=client_credentials" | jq -r .access_token)

curl -s -H "Authorization: Bearer $TOKEN" \
  "https://graph.microsoft.com/v1.0/users?\$top=5&\$select=displayName,userPrincipalName"

PowerShell (Microsoft Graph PowerShell SDK)

Install-Module Microsoft.Graph -Scope CurrentUser

$secret = ConvertTo-SecureString "<client-secret>" -AsPlainText -Force
$cred   = New-Object System.Management.Automation.PSCredential("<client-id>", $secret)

Connect-MgGraph -TenantId "<tenant-id>" -ClientSecretCredential $cred -NoWelcome
Get-MgUser -Top 5 -Property DisplayName,UserPrincipalName

Python (msal + requests)

import msal, requests

app = msal.ConfidentialClientApplication(
    client_id="<client-id>",
    client_credential="<client-secret>",
    authority="https://login.microsoftonline.com/<tenant-id>",
)

result = app.acquire_token_for_client(
    scopes=["https://graph.microsoft.com/.default"]
)

if "access_token" not in result:
    raise SystemExit(result.get("error_description"))

resp = requests.get(
    "https://graph.microsoft.com/v1.0/users?$top=5",
    headers={"Authorization": f"Bearer {result['access_token']}"},
)
print(resp.json())
A 200 response with user objects means the registration, credential and consent all work. Read secrets from environment variables or a vault in real code, never from literals. To confirm which permissions a token carries, paste it into jwt.ms and check the roles claim.

Troubleshooting

Error Likely cause Fix
AADSTS7000215 Invalid client secret provided You copied the secret ID instead of the secret Value, or the secret has a typo Create a new secret and copy the Value column immediately
AADSTS7000222 Client secret keys expired The secret passed its expiry date Add a new secret or certificate and update the app’s config
AADSTS700016 Application not found in the directory Wrong client ID, or wrong tenant in the token URL Re-check both IDs on the Overview pane
AADSTS70011 Invalid scope scope is not https://graph.microsoft.com/.default for the client credentials flow Use .default for app-only tokens
401 Unauthorized Token missing, expired (about 1 hour) or sent to the wrong API Request a fresh token and send it as Authorization: Bearer <token>
403 Authorization_RequestDenied Permission not added, admin consent not granted, or the wrong permission type Check API permissions for a green consent check; decode the token and confirm the roles claim
403 after adding a permission The old token was issued before consent Request a new token after granting consent
If a call still fails, copy the request-id and date from the response headers; they let you trace the request in the Entra sign-in logs (Monitoring & health > Sign-in logs > Service principal sign-ins).

Security best practices and maintenance

    • Prefer federated credentials or certificates over secrets, and never embed credentials in code, scripts or container images.
    • Keep secret lifetimes short and put a calendar reminder or alert on each expiry date.
    • Rotate without downtime: add the new credential, deploy it, confirm it works, then delete the old one.
    • Grant least privilege, and limit scope further where Graph supports it (for example Sites.Selected, or Exchange application access policies for mail).
    • Name an owner or two under Owners in the app registration so the app is never orphaned.
    • Use one registration per app and environment (dev, test, prod) so credentials and permissions can be changed independently.
    • Review regularly: audit Enterprise applications for unused apps and over-broad permissions, and watch service principal sign-in logs for unexpected activity.
    • Protect the app with Conditional Access for workload identities if your tenant licenses it.

References

Portal labels and menu paths change from time to time; the two Learn articles above are the first two worth rechecking if a step no longer matches what you see.

Leave a Reply

Your email address will not be published. Required fields are marked *