[URGENT] ADF WebActivity + SharePoint REST API app-only with Sites.Selected — "Unsupported app only token" even with valid AAD token

Happiest MS User 0 Reputation points
2026-07-07T10:39:23.64+00:00

We have Azure Data Factory (not the fabric version) pipelines that read and write files from SharePoint Online document libraries. The pipelines use a WebActivity to manually obtain an AAD bearer token, then call the SharePoint REST API (/_api/) with that token.

Setup:

  • Azure AD App Registration with Sites.Selected Application permission on SharePoint (admin consent granted)
  • Site-level permission granted via Grant-PnPAzureADAppSitePermission
  • Token obtained via client_credentials flow with client secret (not certificate):
  • The JWT token decoded on jwt.ms shows "roles": ["Sites.Selected"] and correct aud
  • Token is sent as Authorization: Bearer <token> header to SharePoint REST API

Error:


HTTP 401 — "Unsupported app only token."

This happens on every SharePoint REST API call, including a simple GET /_api/web/title.

Research so far:

We found a Microsoft Tech Community article ("Avoiding Access Errors with SharePoint App-Only Access", Oct 2025) stating that SharePoint REST API app-only access requires a certificate, not a client secret. However, we want to confirm whether this is truly the only option, or if there is a supported way to use the SharePoint REST API with a service principal + client secret + Sites.Selected in ADF without a certificate.

Environment:

  • Azure Data Factory (not Fabric)
  • SharePoint Online (Microsoft 365)
  • Authentication: AAD App Registration, client_credentials flow, client secret

Background / What broke and why:

Our ADF pipelines were working correctly until recently, when they started failing with the following error:

"x-ms-diagnostics": "3000014;reason=\"Token type is not allowed due to SharePoint ACS retirement.\";category=\"invalid_request\""

The pipelines were using the legacy SharePoint ACS authentication (Access Control Service), obtaining tokens from:

POST https://accounts.accesscontrol.windows.net/{tenantId}/tokens/OAuth/2

with client_id in the format {appId}@{tenantId} and resource=00000003-0000-0ff1-ce00-000000000000/{tenant}.sharepoint.com@{tenantId}.

This stopped working because Microsoft retired SharePoint ACS for SharePoint Online.

What we did to migrate:

We created a new Azure AD App Registration (replacing the old ACS app principal), granted it Sites.Selected (Application permission) on SharePoint with admin consent, performed the per-site grant via Grant-PnPAzureADAppSitePermission for the two relevant sites, and updated the pipeline to use the new AAD token endpoint:

POST https://login.microsoftonline.com/{tenantId}/oauth2/v2.0/token
scope=https://{tenant}.sharepoint.com/.default

The token is now correctly issued by AAD with "roles": ["Sites.Selected"] (verified on jwt.ms). However, all SharePoint REST API calls now return:

HTTP 401 — "Unsupported app only token."

Current situation and questions:

We are now blocked and unsure which of the following is the actual root cause of the "Unsupported app only token" error:

  1. Missing or incorrectly applied per-site grant — the Grant-PnPAzureADAppSitePermission command may not have been executed by a user with the SharePoint Administrator role, potentially resulting in a silent no-op.
  2. Fundamental limitation of client secret + SharePoint REST API — a Microsoft Tech Community article ("Avoiding Access Errors with SharePoint App-Only Access", Oct 2025) states that SharePoint Online requires certificate-based authentication for app-only access to the REST API, making client secret insufficient regardless of permissions. If this is the case, introducing a certificate would require an Azure Function proxy (since ADF WebActivity cannot sign JWTs natively), or we would need to migrate all calls to Microsoft Graph API.

We would like to know:

  • Is it confirmed that the SharePoint REST API (/_api/) rejects app-only tokens obtained with a client secret, even with Sites.Selected correctly granted? Or does a correctly configured per-site grant make it work?
  • Can ADF's native HTTP Linked Service with authenticationType: ServicePrincipal and resource: https://{tenant}.sharepoint.com bypass this limitation?
  • Is Microsoft Graph API with Sites.Selected the recommended path forward for ADF + SharePoint file access without certificates? In a nutsell, do I need to refactor from sharepoint API to graph API?
Azure Data Factory
Azure Data Factory

An Azure service for ingesting, preparing, and transforming data at scale.

0 comments No comments

1 answer

Sort by: Most helpful
  1. Jerald Felix 16,420 Reputation points Volunteer Moderator
    2026-07-08T01:51:33.31+00:00

    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:

    1. Yes, it is confirmed. SharePoint REST API app-only calls reject client-secret tokens outright. A correctly configured Sites.Selected grant will not fix it because the rejection happens at the token-type level, not the authorization level.
    2. No. ADF's HTTP/REST Linked Service with authenticationType: ServicePrincipal (or AadServicePrincipal) 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.
    3. 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.

    Was this answer helpful?

    0 comments No comments

Your answer

Answers can be marked as 'Accepted' by the question author and 'Recommended' by moderators, which helps users know the answer solved the author's problem.