How to Find Out Why a User Has Access to a SharePoint Site
Trace direct, inherited and nested group access in SharePoint Online, from the role assignment on the resource down to the identity, using the admin UI, REST and PowerShell.
"Why does this person have access?" is the question that makes SharePoint administrators sigh. The Check Permissions dialog will tell you that a user has Read or Edit, and sometimes through which group, but it stops at the first hop. If that group is an Entra ID security group with nested members, or the access came through a sharing link, you are on your own.
This guide walks the full chain, in the order SharePoint actually evaluates it, and shows how to inspect each link with tools you already have.
How SharePoint decides whether a user has access
SharePoint Online authorization is built from three things:
- Securable objects. Sites (webs), lists and libraries, folders and items. Each securable object either inherits permissions from its parent or has unique permissions (its own role assignments).
- Role assignments. A role assignment binds a principal to one or more role definitions (permission levels such as Read, Edit, Contribute, Full Control) on a securable object.
- Principals. A principal can be a user, a SharePoint group, an Entra ID security group, a Microsoft 365 group, a special claim such as Everyone except external users, or a sharing link (which SharePoint implements as a hidden SharePoint group).
When a user opens a document, SharePoint walks up from the item to the nearest securable object with unique permissions (the inheritance boundary), evaluates every role assignment on that boundary, and unions the permissions of every principal the user matches, including groups the user belongs to. The effective permission is the union of all matching paths, so a user with Read through one group and Edit through another gets Edit.
Step 1: Find the inheritance boundary
Start from the resource, not the user. In the modern UI, open the library or folder, choose Manage access, then Advanced. The permissions page states whether the object "has unique permissions" or "inherits permissions from its parent."
With REST you can check any securable object directly. For a folder in a library:
GET https://contoso.sharepoint.com/sites/Finance/_api/web/GetFolderByServerRelativeUrl('/sites/Finance/Shared Documents/Payroll')/ListItemAllFields?$select=HasUniqueRoleAssignments
HasUniqueRoleAssignments: true means this folder is a boundary. If it is false, move up: the library, then the web, then the parent web, until you find one that is unique. The root web of a site collection is always a boundary.
With PnP PowerShell the same check looks like this:
Connect-PnPOnline -Url https://contoso.sharepoint.com/sites/Finance -Interactive -ClientId $ClientId
$folder = Get-PnPFolder -Url "Shared Documents/Payroll" -Includes ListItemAllFields.HasUniqueRoleAssignments
$folder.ListItemAllFields.HasUniqueRoleAssignments
Step 2: List the role assignments on the boundary
Once you know which object holds the effective permissions, list its role assignments with the member and the bound role definitions expanded:
GET https://contoso.sharepoint.com/sites/Finance/_api/web/GetFolderByServerRelativeUrl('/sites/Finance/Shared Documents/Payroll')/ListItemAllFields/RoleAssignments?$expand=Member,RoleDefinitionBindings
Each entry tells you the principal (Member.Title, Member.PrincipalType, Member.LoginName) and the permission levels bound to it. PrincipalType values you will see:
| PrincipalType | Meaning |
|---|---|
| 1 | User (including guests) |
| 4 | Entra ID security group or Microsoft 365 group |
| 8 | SharePoint group |
A login name that starts with c:0t.c|tenant| is an Entra ID security group; c:0o.c|federateddirectoryclaimprovider| is a Microsoft 365 group; c:0-.f|rolemanager|spo-grid-all-users/ is the Everyone except external users claim. A SharePoint group named SharingLinks.<guid>.<type>.<guid> is a sharing link on a specific item.
If the user you are investigating appears directly here (PrincipalType 1), you have your answer: a direct assignment. Note who is bound and stop. Most of the time, though, the user is not listed, and the interesting work begins.
Step 3: Expand the SharePoint groups
SharePoint groups are site-scoped. They can contain users, Entra ID security groups and Microsoft 365 groups, but they cannot contain other SharePoint groups. List the members of each SharePoint group that has a role assignment on the boundary:
Get-PnPGroupMember -Group "Finance Visitors" |
Select-Object Title, LoginName, PrincipalType
If your user is a direct member of the group, the path is Resource -> SharePoint group -> User. If the group contains an Entra ID or Microsoft 365 group (PrincipalType 4), continue to the next step with that group's object id. The object id is the GUID after the last pipe in the login name.
Step 4: Expand Entra ID and Microsoft 365 groups, including nesting
This is the hop the SharePoint UI does not follow. Entra ID security groups can contain other security groups, and SharePoint Online honors that nesting for authorization (Microsoft 365 groups cannot be nested). Use Microsoft Graph's transitiveMembers to flatten the whole tree in one call:
GET https://graph.microsoft.com/v1.0/groups/f1f1f1f1-0000-0000-0000-000000000001/transitiveMembers?$select=id,displayName,userPrincipalName,userType
Or with the Graph PowerShell SDK:
Connect-MgGraph -Scopes "GroupMember.Read.All","User.Read.All"
Get-MgGroupTransitiveMember -GroupId f1f1f1f1-0000-0000-0000-000000000001 -All |
ForEach-Object { $_.AdditionalProperties } |
Select-Object displayName, userPrincipalName, userType
transitiveMembers gives you the flattened result but not the route. If you need to prove which nested group carried the user (an auditor usually does), walk it from the user's side instead:
GET https://graph.microsoft.com/v1.0/users/alex.turner_externalpartner.example%23EXT%23@contoso.onmicrosoft.com/transitiveMemberOf?$select=id,displayName
Intersect the result with the group ids you found in step 3. Any group that appears in both is a path. For a three-level nest you will find the SharePoint group's Entra ID member, the nested group, and the user's direct group all in the list.
Step 5: Check sharing links and item-level exceptions
Sharing links are principals too. A "Specific people" link on a single file creates a hidden SharePoint group on that item, breaks the item's inheritance and grants the group Read or Edit. The user never appears in any site group. To find these, list the role assignments on the item itself, not the folder:
Get-PnPListItem -List "Documents" -Id 42 -Fields "FileLeafRef" |
Get-PnPProperty -Property RoleAssignments |
ForEach-Object { Get-PnPProperty -ClientObject $_ -Property Member | Select-Object Title, LoginName }
Anything named SharingLinks.* is a link. Expand it like any other SharePoint group to see who it admits, or, for "Anyone" links, note that there is no membership at all: possession of the URL is the credential.
Step 6: Confirm with effective permissions, then keep the path
Finally, verify your reconstructed path against what SharePoint itself computes. The GetUserEffectivePermissions endpoint returns the permission mask for a specific login:
GET https://contoso.sharepoint.com/sites/Finance/_api/web/GetFolderByServerRelativeUrl('/sites/Finance/Shared Documents/Payroll')/ListItemAllFields/GetUserEffectivePermissions(@u)?@u='i:0%23.f|membership|alex.turner_externalpartner.example%23ext%23@contoso.onmicrosoft.com'
If the mask says the user has Read and your path explains Read, you are done. If the mask shows more than your path explains, there is another path you have not found yet, usually a second group or a link. Effective permissions are a union; every contributing path is a separate answer to "why."
Write the path down in the form Resource -> SharePoint group -> Entra ID group -> (nested group) -> User, with the role at the boundary. That is what a reviewer needs, and it is what the next person investigating this site will need six months from now.
Why this is hard to do at scale, and impossible for the past
Every step above queries current state. If the question is "why did this user have access in March," none of it works: the group membership may have changed, the link may have been removed, the inheritance may have been reset. SharePoint keeps no history of role assignments, and the audit log keeps events for a limited period and tells you that something changed, not what the resulting state was.
FAQ
- Check Permissions says the user has access but does not name a group. Why?
- Check Permissions reports the first matching path it finds and is not exhaustive. Direct item-level assignments, sharing links and nested Entra ID groups are frequently reported without the intermediate hop. Use the REST role assignment query on the inheritance boundary instead.
- Can a SharePoint group contain another SharePoint group?
- No. SharePoint groups can contain users, Entra ID security groups (including mail-enabled security groups) and Microsoft 365 groups, but not other SharePoint groups. Nesting only happens on the Entra ID side.
- The user has Limited Access on the site. What does that mean?
- Limited Access is granted automatically on parent objects when a user receives unique permissions further down, so they can navigate to the item. It is a symptom of an item-level grant or a sharing link below, and a good hint about where to look.
- Does Microsoft Graph show SharePoint item permissions?
- Graph exposes drive item permissions (including sharing links) through the drives API and site-level application permissions through sites/{id}/permissions, but not the full SharePoint role assignment model. For the boundary and role assignment view, SharePoint REST or CSOM is still required.
Related reading
Related guides
All guides- Entra ID10 min read, intermediate
SharePoint Groups vs Microsoft 365 Groups vs Entra ID Security Groups
Three kinds of group grant SharePoint access and they behave differently for scope, nesting, ownership, guests and audit. Here is how each one works and how to tell them apart in a role assignment.
- Historical Access12 min read, advanced
How to Audit Historical SharePoint Permissions
SharePoint keeps no permission history. This guide shows what the audit log can and cannot reconstruct, how to build a baseline plus event approach with PowerShell, and how to state confidence honestly.
- Compliance & Audit10 min read, intermediate
How to Investigate Who Changed SharePoint Permissions
A step-by-step method for attributing a SharePoint Online permission change to an actor and a timestamp, reading the audit record, and reconstructing the before and after state.