I'm trying to use the SharePoint REST API to interact with SharePoint in ways that the Graph API doesn't support (specifically, adding members to a site). I have an Entra app registered as cross-tenant with Sites.FullControl.All, admin consented in the target tenant. It's not possible to have an interactive user sign-in flow as the app runs headless as a daemon. I need access to all sites, including ones that will be created in the future, so granting permission to each of them individually is not an option.
In the below samples, I've set {client_id}
as the application ID of the Entra app, {client_secret}
as the secret generated for it, and {tenant_id}
as the tenant ID of the target tenant (the one with the SharePoint sites I'm trying to access, and the one that has granted admin consent to the Entra app). I've used contoso
as a stand-in for the actual SharePoint subdomain.
I've tried two options, neither have worked.
1:
POST https://login.microsoftonline.com/{tenant_id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded
client_id={client_id}&
client_secret={client_secret}&
scope=https://contoso.sharepoint.com/.default&
grant_type=client_credentials
This gives me a response with a valid-looking access_token
. However, when I try to make any request - for example:
GET https://contoso.sharepoint.com/sites/MySite/_api/web/lists
Authorization: Bearer {access_token}
Accept: application/json;odata=verbose
I get 401 with {'error_description': 'ID3035: The request was not valid or is malformed.'}
. I seem to get the same error regardless of which endpoint I try. However, there are a couple of interesting headers:
WWW-Authenticate: Bearer realm="{tenant_id}",client_id="00000003-0000-0ff1-ce00-000000000000",trusted_issuers="00000001-0000-0000-c000-000000000000@*,D3776938-3DBA-481F-A652-4BEDFCAB7CD8@*,https://sts.windows.net/*/,https://login.microsoftonline.com/*/v2.0,00000003-0000-0ff1-ce00-000000000000@90140122-8516-11e1-8eff-49304924019b",authorization_uri="https://login.microsoftonline.com/common/oauth2/authorize"
x-ms-diagnostics: 3001000;reason="There has been an error authenticating the request.";category="invalid_client"
These seem promising but the authorization_uri
seems to be used for interactive authentication as far as I can tell, and as I've mentioned, that's not an option for me. If there's a way I can use this with just API calls I would love to hear it.
2: From https://stackoverflow.com/a/63335898
POST https://accounts.accesscontrol.windows.net/{tenant_id}/tokens/Oauth/2
Content-Type: application/x-www-form-urlencoded
client_id={client_id}@{tenant_id}&
client_secret={client_secret}&
resource=00000003-0000-0ff1-ce00-000000000000/contoso.sharepoint.com@{tenant_id}&
grant_type=client_credentials
This also gives me a seemingly-valid access_token
. However, making the same API call as before, I get {'error': {'code': '-2147024891, System.UnauthorizedAccessException', 'message': {'lang': 'en-US', 'value': 'Access is denied. (Exception from HRESULT: 0x80070005 (E_ACCESSDENIED))'}}}
. There's also an interesting header: X-MSDAVEXT_Error: 917656; Access+denied.+Before+opening+files+in+this+location%2c+you+must+first+browse+to+the+web+site+and+select+the+option+to+login+automatically.
. If that header is taken at face value then, as I've mentioned, it's not doable because it involves an interactive process, presumably for each site.
I wanted to try https://stackoverflow.com/a/63386756 but I couldn't find a way to get a refresh_token
without using an interactive/user-driven flow. If it's possible to get a refresh token another way I'd be receptive, since I can already get a working MS Graph access token using the Python MSAL library (ConfidentialClientApplication).
It may be worth noting that checking both of the access tokens in jwt.ms shows that neither of them contain a roles
claim. If that's the reason the API requests are failing, please let me know how I can fix it, since I include scopes
in the first and while I didn't include it in the second sample, I did try it with scopes
and it gave me the same issue.
Thanks!