DEV Community


Posted on

Deploy SPFx app using pipeline's Workload Identity federation

My previous post on using Azure DevOps Pipeline with Workload Identity federation described how to use a context of already authenticated service principal (the identity of service connection used by the pipeline), to refresh the existing token and exchange it for a SPO token.

The example I provided was very simple, and probably not a very useful one. On top of that, @eduardpaul pointed out that it doesn't work for deploying apps to the app catalog.

I utilized the Sites.FullControl.All permissions for both Graph and SharePoint APIs, along with AppCatalog.ReadWrite.All for testing purposes (global app catalog). [...] I consistently encountered a "(401) Unauthorized" error as soon as the code reached the Add-PnPApp section (the only different line from your example). Interestingly, a previous app registration with identical permissions (but using a pfx+password) was functioning correctly.

I couldn't sleep if I didn't check it out.

The test: deploy SPFx app

Service Connection

Dev connection

Please note the url displayed on the bottom- it contains the app id (3f1d79ad-xxxx-xxxx-xxxx-51799f7bb56d) I'm configuring in the next step.

API Permissions

Although I really don't like using Sites.FullControl.All, I'm going to be a bit lazy here.

API Permissions

The pipeline

- name: tenantName
  value: "{your-tenant-name}"
- name: siteName
  value: "AppCatalog"
- name: spfxPackage
  value: "spfx/spfx-1-16-1-pnpm.sppkg"


- task: AzurePowerShell@5
  name: DeploySPFx
    azureSubscription: DEV_Connection
    azurePowerShellVersion: latestVersion
    ScriptType: InlineScript
    Inline: |
      Write-Host "##[group]Install/Import  PS modules"
      Install-Module PnP.PowerShell -Scope "CurrentUser" -Verbose -AllowClobber -Force
      Write-Host "##[endgroup]"

      Write-Host "##[group]Who am I"
      $azContext = (Get-AzContext).Account.Id
      $sp = Get-AzADServicePrincipal -ApplicationId $azContext
      Write-Host "##[debug] ServicePrincipal: $($sp.Id)"
      Write-Host "##[endgroup]"

      $url = "https://$(tenantName)"

      try {
        $azAccessToken = Get-AzAccessToken -ResourceUrl $url
        $conn = Connect-PnPOnline -Url "$url/sites/$(siteName)" -AccessToken $azAccessToken.Token -ReturnConnection

        Write-Host "##[debug]Get-PnPConnection"
        Write-Host $conn.Url

        $packageInSite = Add-PnPApp -Path $path -Overwrite  -Publish -SkipFeatureDeployment  -Connection $conn

      catch {
          Write-Host "##[error]$($_.Exception.Message)"
  displayName: Deploy spfx
Enter fullscreen mode Exit fullscreen mode

As you see, I'm using the DEV_Connection service connection. The authentication is done by the pipeline, the identity exists in the same tenant as my SPO site.

The run

Image description

Seems like everything worked out.
The Id you see here (bd6a8b8e-xxxx-xxxx-xxxx-91ad2d479534) is different than the id you saw above. It's still the same identity, though. One is the application id, and the other one is the object id.

Image description

Did it deploy?

Let's check the app catalog:

Image description

Seems like it did. The Federated Identity added and published the package successfully.

"Works for me" :/

I really don't know, @eduardpaul, what the problem with your deployment might be. I hate giving you the "works for me" answer 🙈, but... it really does

One thing that comes to my mind is that maybe you have to wait:

"Managed identity tokens are cached by the underlying Azure infrastructure for performance and resiliency purposes: the back-end services for managed identities maintain a cache per resource URI for around 24 hours. It can take several hours for changes to a managed identity's permissions to take effect, for example. Today, it is not possible to force a managed identity's token to be refreshed before its expiry. For more information, see Limitation of using managed identities for authorization."
Are managed identities tokens cached?

This refers to API permissions (Sites.Selected, Sites.FullControl.All) that you grant using New-MgServicePrincipalAppRoleAssignment.
Permissions granted to SPO site take effect immediately (at least for now, according to my tests)


Using Sites.FullControl.All is not only "quick'n'dirty" but also rather difficult to get in enterprise environment. Myself, even if I could convince global admin that I need it and they can trust me, I think I would never request it. It certainly has its use when testing, as it helps us to isolate the problem, but once we are sure the code works, it's time to go back to minimum required permissions.

The good news is that Sites.Selected works as well. Just make sure you grant FullControl permissions on the AppCatalog site, using for example Grant-PnPAzureADAppSitePermission

Grant-PnPAzureADAppSitePermission -AppId $clientId -DisplayName $displayName -Permissions FullControl

Top comments (0)