In early 2026, Microsoft reached a long-announced milestone: Azure Access Control Service (ACS) for SharePoint was officially retired on April 2, 2026. When a customer reached out with the deadline weeks away, no inventory of which apps used ACS, and a few apps already broken, we turned that uncertainty into a structured, prioritized, fully documented migration — in a matter of days. This is how.

ACS was the original "app-only" mechanism that let apps and scripts authenticate against SharePoint without a user credential. It predated Microsoft Entra ID (formerly Azure AD), and thousands of tenants built on it — custom code, automation scripts, intranet platforms, and marketplace add-ins.

Step 1 — Understand what you're dealing with

Before writing a single line of migration code, I needed to understand exactly what ACS was and why it was worth solving properly rather than patching.

What is Azure ACS?

Azure ACS is a legacy token-based authentication system that SharePoint supported for app-only access. An app registered directly with SharePoint (via /_layouts/15/AppRegNew.aspx), received a Client ID and Client Secret, and used those to obtain access tokens. Microsoft's TokenHelper.cs class — shipped with classic provider-hosted add-in templates — abstracted all of this. In PowerShell, it looked like this:

# Legacy ACS-based PnP connection
Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/MySite" `
    -AppId "<client-id>" `
    -AppSecret "<client-secret>"

Why was it retired?

ACS has fundamental security limitations:

Microsoft Entra ID solves all of these. The retirement was announced years in advance — but that didn't mean organizations were ready.

Side-by-side comparison of Azure ACS legacy app-only authentication versus Microsoft Entra ID certificate-based authentication, contrasting registration, secrets, Conditional Access support, and governance
Azure ACS vs. Microsoft Entra ID app-only authentication — the modern model wins on every governance and security dimension.

Step 2 — Discovery: finding every ACS-dependent app

Every migration starts with one question: what do we actually have? For this customer the answer was unknown — over a decade of SharePoint, multiple third-party products, developer teams that had come and gone, and a pile of automation scripts nobody fully owned.

The answer was the Microsoft 365 Assessment Tool — an open-source tool from the PnP community (github.com/pnp/pnpassessment).

Setting up the assessment

The tool authenticates using a dedicated Entra ID app registration. I set this up with PnP PowerShell:

Register-PnPAzureADApp `
    -ApplicationName "Microsoft365AssessmentToolForACS" `
    -Tenant "contoso.onmicrosoft.com" `
    -Store CurrentUser `
    -GraphApplicationPermissions "Sites.Read.All", "Application.Read.All" `
    -SharePointApplicationPermissions "Sites.Read.All" `
    -GraphDelegatePermissions "Sites.Read.All", "Application.Read.All", "User.Read" `
    -SharePointDelegatePermissions "AllSites.Read" `
    -DeviceLogin

This created a self-signed certificate in the Windows certificate store and registered the Entra app in one step. After capturing the App ID and certificate thumbprint, I granted admin consent via the Entra portal.

Running the scan

With the app configured, the scan was a single command:

.\microsoft365-assessment.exe start `
    --mode AddInsACS `
    --authmode application `
    --tenant contoso.sharepoint.com `
    --applicationid <app-id> `
    --certpath "My|CurrentUser|<thumbprint>"

I monitored progress with .\microsoft365-assessment.exe status and exported the final report once complete:

.\microsoft365-assessment.exe report --id <assessment-id>
Discovery workflow showing the Microsoft 365 Assessment Tool scanning SharePoint sites and producing CSV reports and a Power BI dashboard of ACS principals and add-ins
The discovery workflow: a single assessment run produced CSVs plus a Power BI template — a living audit capability, not a one-time snapshot.

What the assessment revealed

The scan covered 97 sites and produced a full set of CSVs plus a Power BI .pbit template. The results were eye-opening:

FindingCountStatus
ACS Principals9All expired
SharePoint Add-Ins8Mixed — some active
Tenant-scoped principals3FullControl — highest risk
SharePoint-hosted Add-Ins5No ACS dependency, but also retiring

The key insight: all 9 ACS principals had already expired. The apps were either already broken or about to cease functioning. The customer had been running on borrowed time without knowing it.

Step 3 — Analysis: reading between the lines

Raw scan data is just a starting point. The real work is interpretation.

Not all apps are equal

I categorized the discovered apps along two dimensions: scope (tenant vs. site) and app type (vendor product vs. custom code vs. PnP script vs. SharePoint-hosted add-in). The intersection of those dimensions drives both risk and migration path.

Risk stratification matrix plotting discovered apps by scope and type, with tenant-scoped FullControl principals marked highest priority and site-scoped scripts as medium priority
Risk stratification: tenant-scoped FullControl principals top the list; site-scoped scripts have a limited blast radius.
Expired ≠ gone. Every ACS principal here was expired, yet still registered — still in the app catalog, still consuming governance overhead, still a source of confusion. An expired app nobody cleaned up is security noise. Removing them was an explicit part of the plan.

Step 4 — Building the migration plan

With inventory and risk stratification in hand, I built a four-track migration plan. Each track has a different technical path and a different owner.

Track A — Vendor products (Valo Intranet, Plumsail, Saketa)

The rule here: engage the vendor before writing a single line of code. For products like Valo Intranet, the vendor (now Staffbase after acquiring Valo) has their own Entra ID migration path. Hacking around a vendor product's ACS dependency is a maintenance nightmare. The right steps:

  1. Contact the vendor and confirm their Entra ID-compatible version.
  2. Plan the upgrade to their modern release.
  3. Register a new Entra app with appropriate permissions (Sites.FullControl.All for Valo, scoped appropriately for others).
  4. Upload the certificate, configure the product, test on a pilot site.
  5. Decommission the old ACS principal only after full validation.

For marketplace add-ins where the vendor had no migration path (or the product was abandoned), I documented native Microsoft alternatives:

Legacy Add-InModern Replacement
Plumsail Org ChartMicrosoft Visio Org Chart, SPFx Org Chart Web Part
Saketa Employee DirectoryMicrosoft Search (People vertical), Viva Engage, SPFx People Web Part

Track B — Custom app-only code (SiteAdminCApp)

One tenant-scoped principal had a mysterious name: SiteAdminCApp. No documentation, no known owner — a classic scenario in long-lived tenants. The first task was identifying the owner by correlating the App ID against development team records. The code modernization pattern is straightforward, and it was also the moment to right-size permissions:

Before (ACS — TokenHelper.cs):

var accessToken = TokenHelper.GetAppOnlyAccessToken(
    TokenHelper.SharePointPrincipal, siteUrl, realm);

After (Entra ID — MSAL):

var app = ConfidentialClientApplicationBuilder
    .Create("<client-id>")
    .WithCertificate(certificate)
    .WithAuthority($"https://login.microsoftonline.com/<tenant-id>")
    .Build();

var result = await app.AcquireTokenForClient(
    new[] { "https://contoso.sharepoint.com/.default" })
    .ExecuteAsync();

The new registration was created with the minimum necessary permissions rather than inheriting the old FullControl scope. ACS retirement is the right moment to audit and reduce permissions.

Track C — PnP automation scripts

Four site-scoped principals were owned by internal automation scripts doing file operations. The pattern was identical for each: Connect-PnPOnline -AppId -AppSecret. The migration is elegant and well-supported by PnP PowerShell:

Before:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/MySite" `
    -AppId "4fa73701-..." `
    -AppSecret "<secret>"

After:

Connect-PnPOnline -Url "https://contoso.sharepoint.com/sites/MySite" `
    -ClientId "<entra-app-id>" `
    -Tenant "contoso.onmicrosoft.com" `
    -CertificatePath "C:\certs\PnPOperations.pfx"

Rather than creating four separate Entra apps, I consolidated them into a single PnP-FileOperations-EntraID app and used Sites.Selected permission — granting access only to the specific sites it needed:

Grant-PnPAzureADAppSitePermission `
    -AppId "<entra-app-id>" `
    -DisplayName "PnP-FileOperations" `
    -Permissions Write `
    -Site "https://contoso.sharepoint.com/sites/MySite"

This is a material security improvement over the original configuration, where each ACS app had Write permissions granted broadly.

Track D — SharePoint-hosted add-ins → SPFx

Five add-ins were SharePoint-hosted — they ran entirely within SharePoint and didn't use ACS for auth. But the Add-In model itself is retiring, so they still need to move. The modernization path is to SPFx (SharePoint Framework):

SharePoint-hosted Add-In   →   SPFx Web Part
─────────────────────────────────────────────────────
App Web + Custom ASPX Page   →   SPFx WebPart (client-side)
Lists stored in App Web      →   Regular site lists / Microsoft Lists
Custom CSS / Branding        →   Site Themes + SPFx Application Extension
Remote Object Model calls    →   Microsoft Graph API or SharePoint REST

For branding add-ins specifically, I recommended moving to SharePoint's built-in site themes and site designs — no code required for the majority of styling needs.

Step 5 — Executing in phases

With four tracks and multiple apps in each, sequencing mattered. Running everything in parallel creates confusion and makes rollback harder.

Migration timeline spanning roughly six weeks, sequencing vendor outreach, PnP script migration, vendor solution deployment, custom code modernization, SPFx replacement, ACS principal cleanup, and the final tenant-wide ACS disable
The phased timeline — each phase has a clear owner and gate, ending with a reversible tenant-wide ACS disable.
PhaseWhenAction
Phase 0ImmediateConfirm which apps are still actively used
Phase 1Week 1Vendor outreach (Valo, Plumsail, Saketa)
Phase 2Week 1–2Migrate PnP scripts to certificate auth
Phase 3Week 2–3Deploy vendor-updated solutions or replacements
Phase 4Week 3–4Modernize custom provider-hosted app code
Phase 5Week 4–6Replace SharePoint-hosted Add-Ins with SPFx
Phase 6Week 6+Remove all legacy ACS principals and disabled add-ins
Phase 7After validationDisable ACS tenant-wide

The safety gate

The final step — disabling ACS tenant-wide — is the most consequential:

Connect-SPOService -Url https://contoso-admin.sharepoint.com
Set-SPOTenant -DisableCustomAppAuthentication $true

Importantly, this setting is reversible ($false brings it back). I recommended a two-week validation window between Phase 6 and Phase 7, where all modern replacements ran in production before the kill switch was flipped. No app failures during that window? Turn it off.

What I learned

A few lessons stand out that apply well beyond ACS:

  1. Discovery first, always. Running the assessment before anything else was the most important decision. Without it, the migration would have been guesswork. The assessment tool plus Power BI gave the customer a living audit capability.
  2. Expired registrations are still a problem. All 9 principals were expired and non-functional, yet still registered — cluttering the catalog and complicating governance. Cleaning up dead registrations is part of migration, not an afterthought.
  3. Retirements are permission-review opportunities. The original apps had accumulated FullControl everywhere. Every new Entra registration used Sites.Selected or the minimum Graph scope required. The tenant ended up more secure than before.
  4. Vendor products need vendor tracks. Don't work around a vendor's ACS dependency with custom code. Hold them accountable to a timeline and test the output.
  5. Certificates beat secrets. Every modern registration used certificate-based auth — no rotating secrets, none stored in config or leaked in source control. A one-time setup cost with ongoing security dividends.

Conclusion

When the ACS retirement deadline arrived, this customer's environment was ready. The apps that mattered had moved to Entra ID authentication, the obsolete ones were decommissioned, and the tenant's permissions posture was better than it had been in years.

If you haven't run an ACS inventory yet — even post-retirement — you should. The assessment tool will still show you what's registered. Dead ACS principals don't disappear just because the service is retired; they sit there, undocumented, waiting to confuse the next engineer who inherits the environment. Run the scan. Know what you have. Clean it up.

Resources

ResourceLink
M365 Assessment Toolgithub.com/pnp/pnpassessment
Azure ACS Retirement Guideaka.ms/retirement/acs/support
SharePoint Add-In Retirementaka.ms/retirement/addins/support
Migration Guidanceaka.ms/retirement/addins/guidance
SPFx Development Docsaka.ms/spfx
PnP PowerShellpnp.github.io/powershell
All customer details in this article have been anonymized. The architecture diagrams represent general patterns encountered, not specific production configurations.
DS

Dhaval Shah

Cloud Solution Architect — 18+ years across SharePoint, Microsoft 365, and Azure AI.