Once an app can prove who it is, the next question is what it may do. API permissions let an app call an API like Microsoft Graph — and they come in two kinds. Delegated means the app acts as a signed-in user; application means it acts as itself, with no user at all. That single distinction explains why a background app needs no user password.
Parts 1 to 4 covered authentication — proving who an app is. This part starts authorization — deciding what it may do. It is also the concept that clears up the confusion at the heart of a background app: why there is no user, no password, and no MFA anywhere in an app that reads data on its own. Get the delegated-versus-application split, and the whole thing makes sense.
Before the details, one framing that prevents a lot of confusion later: an app identity can be granted permission through two entirely separate systems, and they are not interchangeable.
They live in different places in the portal, are granted differently, and answer different questions ("what APIs may I call?" versus "what Azure resources may I manage?"). Mixing them up is a classic beginner trap. This part is entirely about the first one.
An API permission grants an app the right to call some capability of another API. Microsoft Graph, for example, defines permissions like Mail.Read, User.Read.All, and Files.ReadWrite.All. An app declares which ones it wants under API permissions on its app registration.
Each permission is narrow and specific — Mail.Read grants reading mail and nothing else. This is the "which doors the badge opens" row of the badge analogy: a permission is one door.
Every API permission comes in one of two forms, and this is the distinction that matters most:
| Delegated | Application (app-only) | |
|---|---|---|
| Acts as | a signed-in user | the app itself |
| User present? | yes | no |
| Effective permission | app's grant and the user's own rights | exactly the app's grant, tenant-wide |
| Consent | user or admin | admin only |
| Appears in the token as | scp (scopes) | roles |
| Typical use | interactive, user-facing apps | daemons, background jobs, CI, services |
The deepest difference is what the permission actually lets the app touch. For delegated, the effective permission is the overlap of two things: what the app was granted, and what the signed-in user can do. For application, there is no user to narrow it, so the effective permission is simply the full grant:
Delegated Mail.Read:
what the APP may do (Mail.Read) AND what the USER may access
-> effective = the overlap: only the mailboxes THIS user can read
Application Mail.Read:
what the APP may do (Mail.Read) (no user in the picture)
-> effective = exactly that: EVERY mailbox in the tenantSo the same permission name means very different reach depending on the kind. Delegated Mail.Read in a user-facing app reads only the current user's mail; application Mail.Read in a background service can read all mail in the tenant, because nothing scopes it to a person.
Here is the payoff — the thing that confuses newcomers about a background app. With application permissions, there is no user in the flow at all. The app authenticates as itself (with its credential from Part 4) and acts under its own granted permission. There is no user to sign in, so:
That is why a background email reader has a ClientId, TenantId, and a credential, but nowhere for a mailbox password to go — the mailbox address is the target, and the app's own identity is the actor. If a design seems to be missing "the user's password," the answer is almost always that it is app-only, and there is no user by design.
Listing a permission on the app registration only requests it — it does nothing until someone consents. Consent is what actually creates the grant, and (tying back to Part 3) the grant attaches to the service principal, not the registration.
Admin consent is the green "Granted for [tenant]" state in the portal. The tenant name in that button — "Granted for Contoso," "Grant admin consent for Contoso" — is the organization/tenant, not a person: it means "approve this app across the whole Contoso directory." Consenting once covers every matching resource in the tenant (the Part 2 "same building, one badge" idea), which is exactly why one grant lets the app reach many mailboxes.
Who can click that button, and why. Admin consent can only be given by someone holding an admin role — a Global Administrator, or a delegated Application/Cloud Application Administrator. The tenant's own administrator can grant it for their whole organization, because that is what the role authorizes. This is also why, on someone else's tenant, a developer who is not an admin cannot self-consent an app-only permission — an admin there has to do it. In your own tenant, you are that admin, so you can grant it yourself.
Its absence is a top cause of "I added the permission but calls still fail": the permission was requested but never granted.
A note on the default
User.Read. Every new app registration comes withUser.Read(Delegated) already added. It lets a signed-in user read their own profile, is added automatically, needs no admin consent, and is harmless — an app-only workload never uses it. Leave it; the permission that matters for app-only mail is theMail.ReadApplication permission added alongside it. The quickest way to confirm you added the right one: it shows Type: Application and Admin consent required: Yes, whereasUser.Readshows Delegated and No.
The failure to recognise: an app that authenticates fine (it got a token) but is refused when it calls the API — a 403 — usually has an unconsented or missing permission, not a credential problem. Authentication succeeded (Part 4); authorization did not. Check that the permission is present and shows admin consent granted.
The two kinds show up differently inside the access token (which Part 7 covers in full). Delegated permissions appear in a scp (scopes) claim; application permissions appear in a roles claim. Decoding a token and seeing Mail.Read sitting in the roles array is the concrete confirmation that an app-only permission is really in effect — a genuinely useful debugging move.
The decision is simple once the split is clear:
A user-facing web app that shows someone their own mail is delegated. A nightly service that processes many mailboxes is application.
Because application permissions are tenant-wide, a natural worry follows: app-only Mail.Read can read every mailbox, which is often more than intended. The permission system alone does not narrow it — there is no user to limit it. Restricting an app-only identity to specific resources requires resource-specific controls layered on top. For mail, Exchange offers exactly this: an Application Access Policy (also called a Resource Application-Only Policy) scopes an app-only identity to a chosen set of mailboxes — typically the members of a security group — so the app can read those and no others. Its newer, recommended successor is RBAC for Applications, which grants the permission scoped from the start rather than bolting a restrict-rule onto a tenant-wide grant. Both are configured through Exchange Online PowerShell (there is no portal button for either — which is exactly why this control is so hard to find in the UI). Other services have their own equivalents.
The important thing to recognise is how this failure looks, because it is not the usual missing-permission error. When a mailbox is outside the policy's scope, the permission is still fully granted and consented — the call simply gets blocked by the policy, with a message about a tenant-configured app-only access policy rather than a missing permission. So one mailbox can succeed while another fails, with the same app, same token, and same code: the difference is only whether that mailbox is inside the policy's group. Fixing it is a tenant-configuration change (adding the mailbox to the policy's group), made by an Exchange admin — not a code or permission change. The key awareness: app-only is broad by default, and narrowing it is a separate, deliberate step that can also cause access to be refused for resources left out of the scope.
The kind of permission drives how a token is requested. Application permissions use the client-credentials flow and the .default scope:
// app-only: no user, .default scope, client-credentials flow
string[] scopes = { "https://graph.microsoft.com/.default" };
var result = await app.AcquireTokenForClient(scopes).ExecuteAsync();
// the returned token carries the app's APPLICATION permissions in its "roles" claim.default means "every application permission already consented for this app" (more on it in Part 7). Delegated permissions instead name specific scopes and are acquired through a user sign-in flow, not AcquireTokenForClient:
// delegated: specific scopes, acquired through a user sign-in flow (not client credentials)
string[] scopes = { "Mail.Read" };
// the resulting token carries these in its "scp" claim, limited by the user's own accessMail.Read is tenant-wide; scoping it to specific resources is a separate step.API permissions let an app call an API, in one of two kinds. Delegated means the app acts as a signed-in user, limited to that user's own access. Application means the app acts as itself, with no user — which is exactly why an app-only workload needs no user password, and why its permission is tenant-wide. Permissions must be consented, and application permissions always require an admin.
That was the first permission system — for calling APIs. Part 6 covers the second, completely separate one: Azure RBAC roles, which govern managing Azure resources (deploying an app, reading a Key Vault, writing to storage). It explains roles like Contributor and Reader, the levels at which they can be assigned, and — most importantly — how to keep these straight from the API permissions in this part, since confusing the two is the most common authorization mistake of all.