Calling SharePoint REST from Power Automate with a certificate-based service app

Classic SharePoint REST (_api/web/...) is still needed for some things, because Microsoft Graph doesn't cover everything yet, and some commands are rather complex to execute (such as working with Managed Meta Data fields) or list-level role assignments. The catch: SharePoint REST app-only auth requires certificate-based tokens. Client secrets are rejected. They work for Graph, but not for classic SP REST.

Requirements on the app registration

  • Graph API → Application permission Sites.Selected (admin consented)
  • SharePoint API → Application permission Sites.Selected (admin consented). This is a separate entry from Graph's, and both are needed

One-time per-site grant

With Sites.Selected, each site needs an explicit grant. This is done with PnP PowerShell, using a different, higher-privileged identity (an admin app/account with delegated Sites.FullControl.All):

Connect-PnPOnline -Url "https://<tenant>.sharepoint.com/sites/<sitename>" -Interactive

Grant-PnPAzureADAppSitePermission `
  -AppId "<app-id>" `
  -DisplayName "<label for reference>" `
  -Site "https://<tenant>.sharepoint.com/sites/<sitename>" `
  -Permissions Write   # or Read / Manage / FullControl

# Verify:
Get-PnPAzureADAppSitePermission -Site "https://<tenant>.sharepoint.com/sites/<sitename>"

From here the paths split. An app registration can hold multiple certificates at once, so it's fine (even useful) to keep a throwaway test cert (Path 1) alongside the real production one (Path 2) without them interfering.

Path 1: Manually-signed cert (mostly for testing)

Use this to validate the whole chain (permissions, site grant, REST calls) before touching Key Vault at all.

1. Generate a throwaway cert

$cert = New-SelfSignedCertificate `
  -Subject "CN=<name>-PostmanTest" `
  -CertStoreLocation "Cert:\CurrentUser\My" `
  -KeyExportPolicy Exportable -KeySpec Signature `
  -KeyLength 2048 -KeyAlgorithm RSA -HashAlgorithm SHA256 `
  -NotAfter (Get-Date).AddMonths(3)   # short-lived is fine, it's throwaway
Export-Certificate -Cert $cert -FilePath "C:\certs\test.cer"

$pwd = ConvertTo-SecureString -String "<any password>" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\certs\test.pfx" -Password $pwd

Upload test.cer (the public half) to the app registration's Certificates & secrets. Also remember the password.

2. Convert the .pfx to base64

[Convert]::ToBase64String([IO.File]::ReadAllBytes("C:\certs\test.pfx"))

Copy the output.

3. Build a throwaway test flow

Create a new flow with a manual trigger and add the premium HTTP action. Not "Send an HTTP request to SharePoint": that connector doesn't support certificate auth.

  • Method: GET
  • URI: https://<tenant>.sharepoint.com/sites/<sitename>/_api/web?$select=Title
  • Headers: Accept = application/json;odata=verbose
  • Authentication → Active Directory OAuth
  • Authority: blank (or https://login.microsoftonline.com)
    • Tenant: your tenant ID
    • Audience: https://<tenant>.sharepoint.com
    • Client ID: the app registration's ID
    • Credential Type: Certificate
    • Pfx: paste the base64 string from step 2
    • Password: the password you set when exporting the .pfx

    4. Run it and confirm

    A successful run returning the site title confirms that token acquisition works, the audience/scope is right, and the Sites.Selected grant is in effect for this app on this site.

    Path 2: Key Vault-managed cert, for production

    Certificate creation (native Key Vault object, with auto-renewal)

    In the Azure Portal:

    1. Navigate to your Key Vault → Objects → Certificates
    2. Click Generate/Import
    3. Method of Certificate Creation: Generate
    4. Certificate Name: e.g. <cert-name>
    5. Type of Certificate Authority: Self-signed certificate
    6. Subject: CN=<descriptive name>
    7. Validity Period: e.g. 24 months
    8. Content Type: PKCS #12 (this is what produces a pfx-retrievable secret, which the flow needs to consume it)
    9. Lifetime Action Type: Automatically renew at a given percentage lifetime
    10. Percentage of lifetime: e.g. 80
    11. Click Create

    This creates a linked Certificate + Key + addressable Secret under the same name in Key Vault, with auto-renewal configured.

    Public half → Entra (manual, at every renewal too)

    1. In the Key Vault → Certificates → select <cert-name> → select the current version
    2. Click Download in CER format to save the public .cer locally
    3. Go to Entra ID → App registrations → your app → Certificates & secrets → Certificates tab → Upload certificate
    4. Select the downloaded .cer and click Add

    Private half → retrieved at runtime, no password

    Nothing to do manually in the portal for this part: it's consumed directly by the flow. Key Vault doesn't attach a password to this exported pfx. In the flow, use the Key Vault connector's Get secret action (name = <cert-name>) and feed its output directly into the HTTP action's Pfx field, with Password left blank.

    Power Automate HTTP action config

    Premium HTTP action (again, not "Send an HTTP request to SharePoint"), with Authentication = Active Directory OAuth:

    • Authority: blank (or https://login.microsoftonline.com)
    • Tenant: your tenant ID
    • Audience: https://<tenant>.sharepoint.com
    • Client ID: the app registration's ID
    • Credential Type: Certificate
    • Pfx: the Key Vault "Get secret" action output, at runtime
    • Password: blank

    Renewal (mostly automatic)

    1. Key Vault auto-renews at the configured threshold: a new key pair is generated internally, under the same secret name, as a new version. Nothing to do in the portal for this step.
    2. Flows referencing the secret by name (unpinned version) pick up the new pfx automatically on their next run. No flow edits needed.
    3. Still manual: Key Vault → Certificates → <cert-name> → select the new version → Download in CER format → Entra → App registration → Certificates & secrets → Upload certificate. The old one stays valid and present, so there's no forced cutover.
    4. Confirm the flows are working on the new cert.
    5. Back in Entra → Certificates & secrets, remove the old certificate entry.
    6. Set a reminder around the renewal window (e.g. tied to the 80% threshold you set) to confirm the Entra upload actually happened. Key Vault renewing internally doesn't guarantee someone remembered the manual Entra half.

    Test order (both paths)

    1. A harmless GET first (_api/web?$select=Title) to confirm auth and the site grant work
    2. Then a write call (e.g. adding a user to a SharePoint group). A successful read doesn't guarantee the granted permission level covers writes

    Known dead ends

    • Managed Identity: not usable. Power Automate cloud flows have no supported way to enable a system-assigned identity for the HTTP action; the field appears in the designer but errors out.
    • Graph-native site permissions (/sites/{id}/permissions): these would require a hybrid model alongside users who already have classic permissions, which isn't worth the complexity.

    Don't stop learning!

    Cassio Milanelo

    Skilled and self-motivated Dynamics 365 CE developer and Power Platform jack of all trades. Acknowledged for high productivity and enthusiasm. Slightly workaholic and coffee lover ☕

    Leave a Reply