Microsoft Graph in Practice #2: App Registration and Auth — Entra ID, Delegated vs. Application Permissions

6 min read

If you ran your first request in Graph Explorer in part 1, the next step is making your own code do the same — and the gateway between the two is authentication, genuinely the hardest part of learning Graph. The API itself is simple; what’s unfamiliar is the structure that answers “in what capacity does my app access whose data, and how far?” This part organizes that structure in the order you actually decide things.

The map up front: ① register an app in Entra ID to create an identity, ② decide delegated or application (this decision drives everything else), ③ add permissions and obtain consent, ④ acquire tokens in code and call.

Why app registration comes first — separating apps from user accounts #

The caller of Graph is a program, not a person. Entra ID demands identity from programs too, and the procedure that creates it is app registration. Registering produces two identifiers.

  • Application (client) ID — the app’s username. Not a secret.
  • Tenant ID — the identifier of your organization (tenant), pointing at your company’s Microsoft 365 space.

The procedure itself is short: Microsoft Entra admin center (entra.microsoft.com) → App registrations → New registration, pick a name, and for internal automation choose “accounts in this organizational directory only” as the supported account type. The Overview screen right after registration shows both IDs. So far this is an empty ID card with no permissions.

Note that app registration may require admin rights depending on organizational settings. If the menu is locked on your work account, the proper route is asking IT for a development app registration — and knowing the permission distinction below makes that conversation far faster.

The most important decision — delegated or application #

Every Graph permission is one of two types. Even the same “read mail” exists in two versions with completely different meanings.

DelegatedApplication
Acting partyThe app on behalf of a signed-in userThe app itself, no user
ReachOnly what that user can seeIf permitted, the whole organization
Typical useInteractive apps, CLI tools, web appsServer batches, daemons, nightly automation
Token acquisitionRequires a user sign-inImmediate, via secret/certificate (client credentials)
ConsentThe user consents (+admin when needed)Always requires admin consent

The decision compresses into one question: “when this code runs, is a human at the screen?” If yes, delegated; if not (a scheduler firing a script at 3 a.m.), application.

The distinction matters because the security weight differs. Delegated is doubly bounded — the app can reach only the intersection of its permissions and the user’s, so an app holding Mail.Read still reads only the signed-in user’s own mail. Application is different: Mail.Read as an application permission can read every mailbox in the organization. That’s why application permissions always require admin consent, and it’s exactly where IT departments start paying close attention.

This distinction returns as a concrete problem later in the series: the API that posts messages to a Teams channel is available with delegated permissions only, so “the server posts an announcement automatically” needs a different route (part 6). This is why checking both columns of the Permissions table in every API doc becomes a habit.

Adding permissions and consent — scopes in practice #

Add Microsoft Graph permissions under API permissions in the app registration. Names follow a readable Resource.Action pattern: User.Read (my profile), Mail.Read, Mail.Send, Files.ReadWrite, Calendars.ReadWrite — and a .All suffix widens the range (delegated Files.ReadWrite.All means all files the user can access; the application version means all files in the organization).

Consent is the approval step — “this app is allowed to use this permission.”

  • Some delegated permissions can be consented by the user at sign-in (subject to org policy).
  • Application permissions and sensitive delegated permissions require admin consent — the “Grant admin consent” button on the API permissions screen. Confirm via the Status column turning green.

The working principle is least privilege. The docs mark the “least privileged” permission for every API; always start there. Taking Mail.ReadWrite where Mail.Read suffices enlarges your own blast radius and slows IT approval. Also worth knowing: application permissions have scoping mechanisms (Exchange’s ApplicationAccessPolicy, SharePoint’s Sites.Selected) that narrow reach to specific resources — offering these alongside your request defuses the “org-wide is too much” objection.

Credentials — secrets and certificates #

Application permissions (and some delegated flows) need the app to prove itself. Two options:

  • Client secret — a string password. Easy to create and fine to start with. It has a validity period (up to 2 years; shorter is recommended), which means expiry equals outage. Operations isn’t writing the date on a calendar — it’s automating an expiry alert.
  • Certificate — register the public key with the app, sign with the private key. More leak-resistant than secrets, the production recommendation, and often mandated by org policy.

Either way, credentials never go into code or the repository. Environment variables or a secret manager (Azure Key Vault and friends) is their home — the goal is never experiencing the committed-secret cleanup procedure. And if the workload runs inside Azure, managed identity — no secret at all — is the best answer.

Token flows — what it looks like from code #

Translating the concepts into code (implementation details come in part 3; this is just the shape):

token flows for the two permission types
[delegated]
app → sends the user to sign in → user authenticates & consents
  → app obtains a token "on behalf of that user" → calls Graph
  (for CLI tools, device code flow: show a code, user enters it in a browser)

[application]
app → requests a token with tenant ID + client ID + secret/certificate
  → obtains the "app's own" token immediately → calls Graph
  (client credentials flow — no human involved)

Tokens are short-lived (about an hour) and the SDK handles renewal, so you rarely touch token strings directly. The one debugging trick — opening a token to inspect its permissions (the scp or roles claim) — appears in part 3.

When auth blocks you — reading the errors #

Auth-stage errors follow fixed patterns. Knowing them in advance saves hours.

  • 401 Unauthorized — the token itself is missing or wrong. Re-check tenant ID, client ID, and the secret value.
  • 403 Forbidden — the token is valid but lacks permission. Almost always one of three: permissions added but admin consent not granted (check the Status column), delegated/application mixed up, or the API requires a different permission entirely. Fastest fix: compare against the doc’s Permissions table.
  • Error codes starting with AADSTS — Entra ID errors. Searching the code finds official docs, and the message text is usually quite specific about the cause.

Summary #

  • The order is app registration (identity) → permission type decision → permissions and consent → tokens in code. Client and tenant IDs are identifiers, not secrets; secrets and certificates are the secrets.
  • The core decision is delegated vs. application: human at the screen → delegated (intersection with the user’s rights); unattended automation → application (org-wide reach, admin consent mandatory).
  • Start from the documented least-privileged permission. Knowing the narrowing mechanisms (ApplicationAccessPolicy, Sites.Selected) speeds approval conversations.
  • Secret expiry equals outage — automate the alert. Production prefers certificates or managed identity.
  • 401 means credentials; 403 means permissions (missing consent, mixed-up types). For AADSTS codes, search first.

Next, we put the Python SDK on top of this and make real calls — implementing both delegated (device code) and application (client credentials) clients.

X