M365Con 2027 – free online conference · 11–22 Jan 2027 · 120 sessions→ m365con.net

2025-08-09

Build App-Only Microsoft Graph Integrations

Build App-Only Microsoft Graph Integrations
Most Microsoft 365 automations fail for the same reason: they rely on fragile delegated permissions tied to human users—leading to session timeouts, MFA prompts, role changes, and broken jobs. The fix is shifting to app-only permissions with Microsoft Graph: run headless, least-privilege, auditable…

TL;DR

  • Root cause of flaky automations: delegated permissions tied to human sessions.

  • Solution: App-only (application) permissions with a service principal → headless, least-privilege, auditable.

  • Outcome: Fewer auth tickets, safer scope, stable night runs, cleaner audits, easier scale.

Why delegated permissions keep breaking you

  • Tied to a user: password resets, MFA, role/licensing changes, offboarding.

  • Encourages over-privileged “service” users to stop errors → risky & messy.

  • Silent failures overnight, “insufficient privileges,” and brittle background jobs.

The smarter path: app-only with Microsoft Graph

  • The app (service principal) is the identity—no interactive login, no session to expire.

  • Least privilege: grant only what the automation needs (per resource/scope).

  • Auditability: actions attributed to the app; simpler compliance/storytelling.

Step-by-step setup (no gotchas)

  1. Register app (Azure AD → App registrations).

  2. Choose “Application” permissions (not Delegated) for Graph scopes you truly need (e.g., Sites.ReadWrite.All, Mail.Send, ChannelMessage.Send).

  3. Admin consent: Global/Privileged admin must grant tenant consent (verify in Enterprise apps → Permissions).

  4. Credentials: Prefer certificates over client secrets; if using secrets, store in Azure Key Vault and rotate.

  5. Tenant objects: Validate target site IDs, team/channel IDs, mailbox scopes; align permission to resource.

  6. Error handling: Map 403/401 responses to missing scopes vs. missing consent; add retries with backoff; log correlation IDs.

Minimum permission recipes (examples)

  • SharePoint file bot: Sites.Selected (best) + per-site consent, or Sites.ReadWrite.All if you must.

  • Teams notifier: ChannelMessage.Send (app) for targeted posting.

  • Mail alerts: Mail.Send (app) on a dedicated mailbox.

  • User provisioning helpers: use narrowly scoped User.ReadWrite.All only if required; prefer Graph lifecycle APIs.

Post-import binding (Power Platform tie-in)

  • After solution import, bind connection references to your app-only connection.

  • Use PAC CLI/PowerShell to set environment variables for tenant IDs, site IDs, channel IDs.

Real-time + reliability

  • Graph webhooks (SharePoint/Teams/Outlook): app-only subscriptions, endpoint validation, renew before expiry.

  • Implement idempotent handlers, DLQ/retry, and rate-limit guards.

  • Centralized logging (App Insights/Log Analytics) + alerting (failure rate, 401/403 spikes).

Security hardening checklist

  • Least privilege → prefer Sites.Selected + per-site grants over tenant-wide scopes.

  • Separate principals per automation domain; no “one ring” app.

  • Key Vault for secrets; managed identity where possible.

  • Conditional Access app exemptions documented; review quarterly.

  • Access reviews: app permissions & owners.

  • Kill switch: disable service principal quickly if needed.

Troubleshooting map (fast)

  • 403 Insufficient privileges → wrong scope or missing admin consent.

  • 401 Unauthorized → bad/expired secret/cert or token audience mismatch.

  • 404 Not found → resource ID/URL wrong or app lacks per-resource grant (e.g., Sites.Selected).

  • 429/503 → add backoff/jitter; respect Graph throttling guidance.

Rollout plan (1–2 sprints)

  • Sprint 1: Register app, grant minimal scopes, wire Key Vault, convert one flaky job (SharePoint upload, Teams notify).

  • Sprint 2: Add webhooks, central logging/alerts, rotate secrets, document support playbook; expand to additional workflows.

Anti-patterns to avoid

  • Global admin on a “service user” to “make it work.”

  • Catch-all *.ReadWrite.All scopes “just in case.”

  • Storing client secrets in code or variables.

  • Skipping admin consent validation—“works on my tenant.”

  • One mega-app for everything (blast radius too big).

Quick-start code sketch (concept)

  • Token: client credentials flow → acquire app token for https://graph.microsoft.com/.default.

  • Call: PUT file to SharePoint drive via site/drive IDs; POST Teams message via channel ID.

  • Retry: on 429/5xx with exponential backoff; log request-id/client-request-id.

FAQ

Do I need E5?
No. App-only uses Azure AD app registrations + Graph; license needs depend on workloads accessed.

Can app-only read all mail?
Only if you grant that scope. Default is no access; grant least privilege and prefer mailbox-specific patterns.

What about Power Automate?
Use app-only connections (custom connector/HTTP with Azure AD) and bind via environment variables in your ALM pipeline.

Spotify · YouTube

Host: Mirko Peters

Related articles

← Build Reusable Components with Power Platform CLIUse Fabric Notebooks for Data Transformation and ML →