403 Forbidden
Situations in the vault where SharePoint, SPFx, Microsoft Graph or the Azure services around them answer with HTTP 403. Each one explains why it happens and how to fix it.
- "File is missing" and "I cannot read the file" collapse into the same null — and the empty state then liesgotchas / rest-apiA T | null helper folds 404, 403, 500 and network failure into one null, and the friendly empty state then lies to the one user who has overdue work; return {ok, status, data} and let only 404 (on every candidate path)…
- /_api/HubSites returns an empty list (HTTP 200) when the caller cannot see the hub sitesgotchas / rest-apiThe hub list is security-trimmed, so an account that cannot read the hub sites gets value: [] with HTTP 200; "this site has no hub" and "no inherited owner" must never be derived from it — read IsHubSite/HubSiteId from…
- A comma in a Graph permissions table can mean AND — searching Teams messages needs two scopesgotchas / graphSearching chatMessage needs Chat.Read and ChannelMessage.Read.All; the 403 spells it out, the table does not (+ webUrl vs webLink from the same endpoint)
- A SharePoint group created by code hides its own membership — and Full Control does not get you ingotchas / securitysitegroups POST defaults to OnlyAllowMembersViewMembership: true, so only a member or the account that ran provisioning can read the members; Full Control does not help and identical permission masks prove it
- A mail-permission probe that can't tell "no mailbox" from "no consent" lies to adminsgotchas / graph/me/messages 404 MailboxNotEnabled* on mailbox-less accounts ≠ missing consent; three verdicts, and never cache a negative probe result
- A permission check that only asks "who has too much?" passes on an empty ACLgotchas / permissions"no principal outside the allow-list can write" is also satisfied when nobody can write at all; assert the opposite direction with a non-zero writer count, and read the principals strictly so a transient 429 cannot…
- A status code measures where the request stopped first, not what the user may dogotchas / permissionsA probe that writes with a deliberately stale IF-MATCH and reads permissions off 403 vs 412 measures which gate the request hit first: SharePoint evaluates the etag BEFORE authorisation, so 412 comes back even where…
- ApplicationAccessPolicy won't scope to a shared mailbox — use a mail-enabled security groupgotchas / graph"not a security principal"; scope app-only Mail.Send to a mail-enabled security group
- Approving Microsoft Graph permissions for SPFx needs a Global Administrator — Application Administrator is not enoughgotchas / permissionsAPI access requests for Microsoft Graph need Global Administrator (Application Administrator only covers third-party APIs), and admin consent for Graph application permissions such as Sites.Selected is explicitly…
- az CLI can't grant Sites.Selected — the scope is blocked by AADSTS65002gotchas / graphsites/{id}/permissions needs Sites.FullControl.All; the CLI client can't request it (AADSTS65002), use the Graph PowerShell SDK
- Reading Teams channel messages needs a Teams license — a Global Admin without one gets 403 even as a team membergotchas / graphDelegated GET /teams/{id}/channels/{id}/messages returns 403 'Failed to get license information for the user' for an unlicensed account even with ChannelMessage.Read.All consented and team membership; a licensed member…
- A throttled permission probe must not downgrade — and cache — the rolegotchas / rest-apiA 429/403 on currentuser/groups resolves to the lowest role; cache it and the user is stuck read-only for the TTL; only persist a confirmed (200) result
- "User:… dont have any permissions" from the audit query API rejects the USER, not the appgotchas / graphConsent vs. Exchange audit role; Global Admin usually inherits it, so compare accounts
- Manage Lists lets you read other people's items on a WriteSecurity 2 list, but not edit them — that takes Override List Behaviorsgotchas / permissionsOn a list with item-level write security, the Edit role (Manage Lists) still gets 403 when it updates an item someone else created; only a role with Override List Behaviors (CancelCheckout, Low 0x100) gets through, so…
- Enumerate every site from the browser console (no admin API, no tooling)gotchas / searchcontentclass:STS_Site OR STS_Web over Search REST lists every web the account can read, no admin role or tooling; plus the traps of a cross-site F12 sweep (Hidden filter, case-sensitive GetList, 400 vs 404, per-web…
- Exchange Online Admin API (the REST behind EXO V3) cannot be called from the browser — no CORS, and SPFx tokens lack Exchange.Managegotchas / graphadminapi/beta/{tenant}/InvokeCommand fails with 'Failed to fetch' even with a fake token; read Exchange config with an admin-run script and import, or use Graph message trace and mailbox reports
- GetStorageEntity returns 200 for a missing key, not 404gotchas / rest-apiUnset tenant property answers 200 {"odata.null":true}; "unset" and "failed" are indistinguishable by status code, and writing needs Tenant Admin — same trap in …?$select=Exists, which returns 200 {"Exists":false}
- A Graph scope your SPFx solution never asked for may already work — grants are tenant-widegotchas / spfxwebApiPermissionRequests is a request, not a grant; an approval lands on one tenant-wide principal, so a scope another solution had approved already works in yours (and may be missing at the customer)
- A provisioned list with ReadSecurity=2 looks perfect to an admin and empty to everyone elsegotchas / listsReadSecurity=2 looks perfect to an admin and empty to everyone else; WriteSecurity=2 breaks collaborative edits only
- Locking down a no-script site from code: web NoCrawl is denied even to a site collection admin, and turning off access requests returns 200 and changes nothinggotchas / securityOn a modern site with custom scripts disabled, NoCrawl on the WEB returns 403 via REST and CSOM even for a site collection admin, and SetUseAccessRequestDefaultAndUpdate(false) returns 200 without effect; both work…
- The public Microsoft 365 roadmap JSON has no CORS headers — an SPFx web part can't fetch itgotchas / spfxreleasecommunications/api/v1/m365 answers 200 without Access-Control-Allow-Origin; proxy it server-side
- A provisioning step gated on success runs forever for every non-admingotchas / spfxA startup step needing ManageLists writes no marker for ordinary members, so the whole batch replays on every page load; the marker needs two states
- Creating a team right after creating its Microsoft 365 group returns 404 — retry only the 404, and don't log the failure as successgotchas / graphPUT /groups/{id}/team fails with 404 for minutes after POST /groups (replication); retry only 404, surface a real failure as a warning
- View counts and ratings written by readers cannot live in a list only editors may writegotchas / listsViews and ratings are written by the reader, so on a WriteSecurity: 4 content list every click is a swallowed 403 while the UI says thanks
- A setup marker that measures only part of the step freezes a half-provisioned site forevergotchas / spfxThe success flag measured the id lookups, not the addroleassignment POST, so a 403 froze a site with groups that carry no permission level at all
- signInActivity without Entra ID P1 — the retry without it turns "not measured" into "0 inactive users"gotchas / graphSelecting signInActivity on /users fails with 403 in tenants without Entra ID P1/P2; the common fallback (retry without the property) makes every user look "never signed in" or gets skipped, so inactive-user and…
- SP.WebProxy is add-in-only — from SPFx it 403s with "without an app context"gotchas / spfxNo SharePoint-native CORS proxy for SPFx; the 403 hides inside an HTTP 200
- Tenant-wide enumeration in Graph is app-only — check Delegated vs Application before you buildgotchas / graphgetAllSites rejects delegated tokens; check the Permissions table first
- Version-gated provisioning silently drops the settings only an admin could writegotchas / listsA settings PATCH needs Manage Lists; the first member to open the app after an upgrade burns the one attempt and the version is stored anyway
- A list with WriteSecurity 4 ignores Contribute — only ManageLists gets throughgotchas / permissionsOnly ManageLists (Design/Edit/Full Control) overrides the item-level write block, so "break inheritance + give the group Contribute" grants nothing; resolve the role by RoleTypeKind, not by its localised name
- X-RequestDigest expires ~30 min after page load — but adding it by hand in SPFx makes things worsegotchas / rest-apiWrites 403 "security validation is invalid" on a long-open page; raw fetch needs a fresh digest from /_api/contextinfo, but in SPFx a hand-set header switches SPHttpClient's own digest and 403-recovery OFF