An Azure service for ingesting, preparing, and transforming data at scale.
Hello Happiest MS User ,
Greetings! Thanks for raising this question in the Q&A forum.
The good news is that this is not a per-site permission problem, so Grant-PnPAzureADAppSitePermission is not your root cause. The "Unsupported app only token" error is thrown by the classic SharePoint REST API (/_api/) purely based on how the token was issued, before SharePoint even evaluates the roles claim or the site grant. When you request a token with client_credentials and a client secret, Azure AD stamps the token with "appidacr": "1". When you request it with a certificate-based client assertion, the token gets "appidacr": "2". The SharePoint REST API only accepts tokens with appidacr=2, regardless of whether Sites.Selected and the site grant are configured correctly. This is a long-standing SharePoint Online restriction that became visible for you once ACS was retired and you moved to the AAD v2 token endpoint, because ACS tokens did not carry this restriction the same way.
To answer your three questions directly:
- Yes, it is confirmed. SharePoint REST API app-only calls reject client-secret tokens outright. A correctly configured
Sites.Selectedgrant will not fix it because the rejection happens at the token-type level, not the authorization level. - No. ADF's HTTP/REST Linked Service with
authenticationType: ServicePrincipal(orAadServicePrincipal) does not bypass this unless you switch the credential type to certificate-based (servicePrincipalCredentialType: ServicePrincipalCert). Using the same client secret through the linked service instead of a manual WebActivity call will hit the identical error. - Yes, migrating to Microsoft Graph is the recommended path for your scenario, and it is the lower-effort fix compared to introducing certificate signing.
Here is how to proceed, in order of effort:
Step 1: Confirm the token type is really the issue
Decode your current token on jwt.ms and check the appidacr claim. If it reads "1", that confirms the client-secret token rejection described above and rules out the per-site grant as the cause.
Step 2: Migrate SharePoint REST calls to Microsoft Graph (recommended)
Microsoft Graph's SharePoint-backed endpoints (Sites and Drive APIs) accept app-only tokens obtained with a client secret, since Graph does not enforce the appidacr=2 restriction. Your existing Sites.Selected Application permission and site-level grant carry over to Graph, so no re-consent is required. For document library read/write, use the Drive API:
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/{folder-path}:/children
GET https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/{file-path}:/content
PUT https://graph.microsoft.com/v1.0/sites/{site-id}/drive/root:/{file-path}:/content
You can keep the same architecture: continue using WebActivity to get the token (just change scope to https://graph.microsoft.com/.default) and swap the SharePoint REST calls for the Graph calls above. Alternatively, configure an ADF REST Linked Service with authenticationType: OAuth2ClientCredential pointing at Graph, which lets Copy Activity call it directly:
{
"type": "RestService",
"typeProperties": {
"url": "https://graph.microsoft.com/v1.0/",
"authenticationType": "OAuth2ClientCredential",
"clientId": "<app-id>",
"clientSecret": { "type": "SecureString", "value": "<client-secret>" },
"tokenEndpoint": "https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token",
"scope": "https://graph.microsoft.com/.default"
}
}
Step 3: If you must stay on SharePoint REST, use certificate auth via ADF's native REST connector
If Graph does not cover every call you currently make against /_api/, you can keep using the SharePoint REST API by switching to certificate-based auth without building a full Azure Function proxy, as long as you consume it through the REST Linked Service (not WebActivity, which cannot sign a JWT client assertion itself):
{
"type": "RestService",
"typeProperties": {
"url": "https://{tenant}.sharepoint.com/",
"authenticationType": "AadServicePrincipal",
"servicePrincipalId": "<app-id>",
"servicePrincipalCredentialType": "ServicePrincipalCert",
"servicePrincipalEmbeddedCert": { "type": "SecureString", "value": "<base64-cert>" },
"servicePrincipalEmbeddedCertPassword": { "type": "SecureString", "value": "<cert-password>" },
"tenant": "<tenantId>",
"aadResourceId": "https://{tenant}.sharepoint.com"
}
}
Store the certificate in Azure Key Vault and reference it from the linked service. This gets you the appidacr=2 token automatically, without needing to hand-craft and sign the JWT assertion yourself in a WebActivity or Function.
Step 4: If Copy Activity's REST connector can't express a specific call you need
Some SharePoint REST operations (multipart file uploads, special headers like X-RequestDigest) may not map cleanly onto Copy Activity's REST source/sink. In that case a small Azure Function that signs the certificate-based client assertion and performs the SharePoint call is the fallback, but try Steps 2 and 3 first since they avoid that extra component entirely.
If this answer helps you kindly accept the answer which will help others who have similar questions.
Best Regards,
Jerald Felix.