Summary
With a delegated token that was consented for Files.ReadWrite.AppFolder, every drive endpoint returns 403 accessDenied for a personal Microsoft account — including GET /me/drive/special/approot, which the docs say auto-creates the app folder on first access. Changing exactly one thing — the scope, to Files.ReadWrite — makes the identical requests succeed.
This forces apps that only ever write into their own folder to request full access to the user's entire OneDrive, which is a hard sell on the consent screen and inappropriate least-privilege-wise.
What we built
A B2B SaaS (Rails, plain REST calls against graph.microsoft.com/v1.0, no SDK) that uploads PDF reports into its app folder. Reproduced on 2026-08-25.
Reproduction
- App registration: "Accounts in any organizational directory and personal Microsoft accounts", Web redirect URI.
- API permissions (verified in the portal before consenting):
User.Read, Files.ReadWrite.AppFolder (both delegated, no admin consent required).
- Authorization code flow via
https://login.microsoftonline.com/common/oauth2/v2.0/authorize with scope=offline_access Files.ReadWrite.AppFolder User.Read and prompt=consent. Signed in with a personal Microsoft account; the consent dialog was shown and listed the app-folder permission ("Have full access to the application's folder (preview)").
- Token exchange succeeds;
GET /v1.0/me → 200.
- All of the following return 403
accessDenied / "Access denied" with that token:
GET /v1.0/me/drive
GET /v1.0/me/drive/root
GET /v1.0/me/drive/special/approot
POST /v1.0/me/drive/special/approot:/report.pdf:/createUploadSession
- Re-consented with
scope=offline_access Files.ReadWrite User.Read — no other change (same account, same app registration, same code). Every request above succeeds; createUploadSession + single-range PUT uploads work and the file lands in Apps/<app name> as expected.
(Personal-account Graph tokens are opaque, so we could not inspect the scp claim directly; the behavior was reproduced across two independent consent round-trips.)
Related reports
This appears to be the same long-standing problem as #1667 (403 on createUploadSession under approot, open since 2023, "Needs: Investigation"), #701, and #682 (closed with the workaround "add the broad Files permission", which defeats the scope's purpose). For business accounts the scope is rejected at sign-in altogether (AzureAD/microsoft-authentication-library-for-dotnet#681), so there is currently no account type for which Files.ReadWrite.AppFolder actually works end-to-end.
Comparison with other providers
The equivalent least-privilege grants work as documented elsewhere, and are platform-enforced:
| Provider |
Mechanism |
Result |
| Google Drive |
drive.file scope |
App sees only files it created; non-sensitive scope, no verification burden. Works. |
| Dropbox |
"App folder" access type |
App is jailed to Apps/<app>; enforced server-side. Works. |
| OneDrive |
Files.ReadWrite.AppFolder |
403 on everything (personal), sign-in rejected (business). Only workaround: request full-drive Files.ReadWrite. |
Ask
Either fix Files.ReadWrite.AppFolder for personal accounts (and support it for business), or document an alternative least-privilege path for "app writes only into its own folder". We do not want to ask users for full access to their OneDrive when we only ever create files in Apps/<app name> — users notice the difference on the consent screen, and rightly so.
Summary
With a delegated token that was consented for
Files.ReadWrite.AppFolder, every drive endpoint returns403 accessDeniedfor a personal Microsoft account — includingGET /me/drive/special/approot, which the docs say auto-creates the app folder on first access. Changing exactly one thing — the scope, toFiles.ReadWrite— makes the identical requests succeed.This forces apps that only ever write into their own folder to request full access to the user's entire OneDrive, which is a hard sell on the consent screen and inappropriate least-privilege-wise.
What we built
A B2B SaaS (Rails, plain REST calls against
graph.microsoft.com/v1.0, no SDK) that uploads PDF reports into its app folder. Reproduced on 2026-08-25.Reproduction
User.Read,Files.ReadWrite.AppFolder(both delegated, no admin consent required).https://login.microsoftonline.com/common/oauth2/v2.0/authorizewithscope=offline_access Files.ReadWrite.AppFolder User.Readandprompt=consent. Signed in with a personal Microsoft account; the consent dialog was shown and listed the app-folder permission ("Have full access to the application's folder (preview)").GET /v1.0/me→ 200.accessDenied/ "Access denied" with that token:GET /v1.0/me/driveGET /v1.0/me/drive/rootGET /v1.0/me/drive/special/approotPOST /v1.0/me/drive/special/approot:/report.pdf:/createUploadSessionscope=offline_access Files.ReadWrite User.Read— no other change (same account, same app registration, same code). Every request above succeeds;createUploadSession+ single-range PUT uploads work and the file lands inApps/<app name>as expected.(Personal-account Graph tokens are opaque, so we could not inspect the
scpclaim directly; the behavior was reproduced across two independent consent round-trips.)Related reports
This appears to be the same long-standing problem as #1667 (403 on
createUploadSessionunderapproot, open since 2023, "Needs: Investigation"), #701, and #682 (closed with the workaround "add the broad Files permission", which defeats the scope's purpose). For business accounts the scope is rejected at sign-in altogether (AzureAD/microsoft-authentication-library-for-dotnet#681), so there is currently no account type for whichFiles.ReadWrite.AppFolderactually works end-to-end.Comparison with other providers
The equivalent least-privilege grants work as documented elsewhere, and are platform-enforced:
drive.filescopeApps/<app>; enforced server-side. Works.Files.ReadWrite.AppFolderFiles.ReadWrite.Ask
Either fix
Files.ReadWrite.AppFolderfor personal accounts (and support it for business), or document an alternative least-privilege path for "app writes only into its own folder". We do not want to ask users for full access to their OneDrive when we only ever create files inApps/<app name>— users notice the difference on the consent screen, and rightly so.