INTRODUCTION
During a recent project for a logistics enterprise, we were tasked with deploying an intelligent document processing bot using n8n. The workflow was designed to ingest invoices from email, process them, and upload the structured data into a specific SharePoint document library. The requirements were strict: the bot needed autonomous access to write files, but under a Zero Trust security model, it could not have access to the entire tenant’s file ecosystem.
We realized quickly that the standard Microsoft Graph permission Files.ReadWrite.All was a non-starter. Granting an automated script full read/write access to every CEO memo, HR document, and financial report across the organization was an unacceptable risk. The solution was theoretically simple: use the Sites.Selected permission scope to limit the Azure App Registration to a single site.
However, implementation proved far more complex than the documentation suggested. We encountered a situation where standard PnP.PowerShell commands failed repeatedly with authentication errors, creating a bottleneck in our deployment pipeline. This challenge inspired this article, detailing how to correctly configure granular SharePoint permissions without falling into authentication loops.
PROBLEM CONTEXT
The architecture involved an n8n automation engine requiring headless authentication against Microsoft 365. To achieve this, we utilized an Azure App Registration (Service Principal). The goal was to ensure this Service Principal had “Write” access to exactly one site—let’s call it Logistics_Invoices—and zero access to the rest of the SharePoint tenant.
The standard mechanism for this is the Sites.Selected API permission in Microsoft Graph. Unlike Sites.Read.All, this permission does not grant access by default; it acts as a gatekeeper. Once assigned, an administrator must explicitly “grant” the application access to specific sites via a POST request or PowerShell command.
The issue surfaced during this granting phase. Our DevOps engineers attempted to use the PnP.PowerShell module to map the Azure App to the specific site but were blocked by persistent parameter set errors and AADSTS7000218 authentication failures.
WHAT WENT WRONG
The team faced a cascade of errors that are common when bridging legacy PowerShell authentication with modern OAuth flows. Here is the breakdown of the failure points:
- Conflicting Scopes: The team initially added
Files.ReadWrite.AllalongsideSites.Selected. This defeats the purpose; if you grant “All,” the specific selection becomes redundant or ignored, leaving the tenant exposed. - The “Client Assertion” Error (AADSTS7000218): When attempting to connect via PnP using a Client ID, the system rejected the request because the App Registration was configured as a “Confidential Client” (Web App) but the PowerShell command was treating it like a “Public Client” (Desktop App) without a secret, or sending the secret incorrectly.
- Parameter Set Conflicts: The error “Parameter set cannot be resolved” indicated that the team was mixing authentication flags that cannot coexist (e.g., trying to use Interactive login while simultaneously passing a Client Secret).
- The Permissions Paradox: The most critical oversight was trying to use the target application’s credentials to run the
Grant-PnPAzureADAppSitePermissioncommand. The target app (the bot) cannot grant itself permissions. The command must be run by a Global Admin or SharePoint Admin context, which requires a completely different authentication flow than the bot uses.
HOW WE APPROACHED THE SOLUTION
To resolve this, we had to decouple the “Control Plane” (the Admin configuring the access) from the “Data Plane” (the Bot accessing the files).
We identified that PnP.PowerShell requires a specific approach when managing App-only permissions. We couldn’t just “login as the app.” We needed to log in as an Administrator interactively to the SharePoint Admin Center URL, and from that privileged session, execute the grant command for the target App ID.
We also realized we needed to clean up the Azure Entra ID configuration to remove broad permissions, ensuring strict adherence to the request: ONE site only.
FINAL IMPLEMENTATION
Below is the verified procedure to resolve the PnP errors and successfully restrict n8n (or any external service) to a single SharePoint site.
Step 1: Correct Azure App Registration
In the Azure Portal, we configured the App Registration for the n8n bot as follows:
- API Permissions: Removed
Files.ReadWrite.All. Added onlySites.Selected(Type: Application). - Grant Admin Consent: Clicked “Grant admin consent for [Tenant]” to activate the scope.
- Certificates & Secrets: Generated a new Client Secret (value saved for n8n configuration).
Step 2: The PowerShell Fix
The specific fix for the connection errors involves connecting to the Admin URL using the WebLogin or Interactive flag with a human admin account, not the App ID. The Grant-PnPAzureADAppSitePermission command must be run by a human admin.
Here is the sanitized script we used to finalize the setup:
# 1. Define Variables
$AdminUrl = "https://[tenant]-admin.sharepoint.com"
$TargetSiteUrl = "https://[tenant].sharepoint.com/sites/Logistics_Invoices"
$AppId = "[Your-Azure-App-Client-ID]"
$AppName = "n8n-Automation-Bot"
# 2. Connect as a Global/SharePoint Admin (Human Interactive Login)
# Do NOT use the Client ID/Secret here. You are the Admin now.
Connect-PnPOnline -Url $AdminUrl -Interactive
# 3. Grant 'Write' Permissions to the specific site
# This tells SharePoint: "Allow this App ID to write to this specific site."
Grant-PnPAzureADAppSitePermission -AppId $AppId `
-DisplayName $AppName `
-Site $TargetSiteUrl `
-Permissions Write
# 4. Verify the permissions were applied
Get-PnPAzureADAppSitePermission -Site $TargetSiteUrl
Step 3: Verification in n8n
Once the PowerShell command succeeded, we configured the Microsoft Graph node in n8n using the App ID and Client Secret. We tested uploading a file to the Logistics_Invoices site, which succeeded. We then attempted to upload to a different site, which failed with a 403 Forbidden error, confirming the security isolation was successful.
LESSONS FOR ENGINEERING TEAMS
This experience highlighted several key takeaways for teams looking to hire software developers for scalable data systems:
- Separate Identity Contexts: Always distinguish between the identity executing the configuration (the Admin) and the identity being configured (the App). Confusing these leads to “Missing Scope” errors.
- Avoid Over-Privileging: Never leave
Files.ReadWrite.Allactive ifSites.Selectedis your goal. It overrides the restriction and opens security holes. - Parameter Set Precision: In PowerShell, specifically PnP, verify which parameter set you are using. Mixing
-Interactive(delegated) with-ClientSecret(application) is a syntax error in logic. - Zero Trust is Manual:
Sites.Selectedis secure by design because it requires manual intervention (or a separate admin pipeline) to grant access. Do not try to automate the “granting” using the app itself. - Tooling Maturity: When you hire .NET developers for enterprise modernization, ensure they are comfortable with PnP.PowerShell and Graph API nuances, as the GUI often lags behind API capabilities.
WRAP UP
Restricting SharePoint access is a critical security requirement for modern enterprise automation. By correctly leveraging Sites.Selected and understanding the authentication boundaries of PnP.PowerShell, we delivered a secure, functional bot workflow that satisfied strict compliance needs. Correctly managing identity protocols prevents costly security breaches and reduces deployment friction.
Social Hashtags
#AzureAD #MicrosoftGraph #SharePointSecurity #ZeroTrust #CloudSecurity #PnPPowerShell #DevOps #AzureAppRegistration #TechArticle #EnterpriseAutomation #M365 #IdentityAndAccessManagement #CyberSecurity
For organizations looking to build secure automation pipelines or contact us to discuss how our dedicated engineering teams can support your next project.
Frequently Asked Questions
This error usually occurs when you try to authenticate with a Client ID (Service Principal) that has a Client Secret (Confidential Client) using a flow designed for Public Clients (like Desktop apps). To fix this, ensure you are using the -ClientSecret parameter if authenticating as the app, or use -Interactive without Client ID to login as a user.
No, Sites.Selected is primarily designed for Application permissions (daemon apps/services) where no user is present. For delegated scenarios, standard user permissions apply based on the logged-in user's access.
No, Microsoft Graph and Modern SharePoint permissions operate at the Site Collection level. You cannot use Sites.Selected to grant access to a specific subsite or folder only; it applies to the entire Site Collection.
You can revoke access using the Revoke-PnPAzureADAppSitePermission command. You will need the Permission ID, which you can retrieve using Get-PnPAzureADAppSitePermission on the specific site.
Success Stories That Inspire
See how our team takes complex business challenges and turns them into powerful, scalable digital solutions. From custom software and web applications to automation, integrations, and cloud-ready systems, each project reflects our commitment to innovation, performance, and long-term value.

California-based SMB Hired Dedicated Developers to Build a Photography SaaS Platform

Swedish Agency Built a Laravel-Based Staffing System by Hiring a Dedicated Remote Team
















