SharePoint Framework CI/CD with GitHub Actions & No Creds!

10 min read

Learn how to set up credential-less CI/CD for SharePoint Framework using GitHub Actions and federated identity, eliminating the need for managing secrets and certificates.


In my recent SharePoint Framework accelerator cohort, a question came up about implementing continuous integration & continuous deployment (CI/CD) in SharePoint Framework (SPFx) projects. I had planned to improve upon the process I showed in my Mastering the SPFx Ultimate bundle course by using managed identities with Microsoft Entra ID. Unfortunately, I discovered this wasn’t supported by the CLI for Microsoft 365, which is the preferred tool for implementing CI/CD in automated workflows.

This limitation meant we had to stick with client certificates to authenticate our Entra ID app for deployment. This works, but it’s no ideal as I’d like to use the latest features in Azure to go credential-less.

But today, I’m thrilled to share a major improvement to this process!

How we got here (or skip ahead)

In this article, I want to start by looking back at how we got here, but if you only want to see what’s new, feel free to skip it using the table of contents in the left margin.

Looking Back

Let me walk you through how we got here.

Initially, CI/CD wasn’t possible for SPFx solutions or any projects using SharePoint app catalogs (including SharePoint Add-ins). We simply didn’t have the APIs needed to manage the package lifecycle: installing, deploying, uninstalling, or upgrading.

That changed in late 2017 when Microsoft introduced the application lifecycle management (ALM) endpoints in the SharePoint REST API. This gave us the ability to automate lifecycle operations for SPFx packages.

Back then, these automations required user authentication. Organizations typically created service accounts for automated deployments, storing credentials in GitHub or Azure DevOps’s secure stores. The downside? Passwords needed regular maintenance and had to be updated when they expired.

These service accounts came with another limitation in that they couldn’t use multi-factor authentication (MFA). Adding MFA would have required manual intervention, which defeats the purpose of automation.

Microsoft later enhanced the ALM APIs with Entra ID app support. A significant improvement came in early 2021 with the Sites.Selected application permission, letting customers grant permissions to specific site collections instead of the entire tenant. But there was still a catch—these apps only worked with client certificates for authentication. Like client secrets, these certificates expire and require ongoing maintenance.

But today, things are a LOT better!

Credential-Less Authentication with Federated Identity

In 2023, Microsoft added support for identity federation with Microsoft Entra ID apps. This feature lets you access Microsoft Entra ID-protected resources without managing secrets for specific scenarios.

Previously, apps needed an identity to authenticate and access resources with other services. With the SharePoint ALM endpoints in the SharePoint REST API, we used client IDs and secrets or certificates to authenticate with an Entra ID app to obtain an identity (access token) for our API calls.

For workloads running in Azure, we could use managed identities where the resource had its own identity. For instance, if you had an Azure Function that needed to write to an Azure Storage Blob, you could simply grant the Azure Function’s managed identity the necessary RBAC role on the Storage account.

This improvement eliminated the need for credentials in our app! We just granted permissions to the Azure resource where our app ran.

But what about resources that didn’t exist in Azure?

That’s where user-assigned managed identities or app registrations come in!

In 2023, Microsoft enabled Microsoft Entra ID to trust tokens from external identity providers (IdP) like GitHub and Google. Once you create a trust relationship, your external process, like a GitHub Action workflow, can exchange trust tokens between the external IdP and the Microsoft identity platform.

The result? Your CI/CD process running in GitHub Actions can now obtain an Entra ID application identity with zero credentials!

Scenario: Deploy Files Directly to Azure Storage Blob from CI/CD Processes

I use this model across many of my projects. For instance, I have an automated process that builds and redeploys this website several times daily. During these workflow runs, I deploy all client-side source map files to an Azure Storage Blob, which enables better debugging when Azure Application Insights logs client-side exceptions.

 1#########################################################
 2# login to Azure CLI via federated credential
 3#########################################################
 4- name: 🔑 Login to Azure
 5  uses: azure/login@v2
 6  with:
 7    client-id: ${{ vars.VTNSIO_ENTRAAPP_CLIENTID }}
 8    tenant-id: ${{ vars.VTNSIO_ENTRAAPP_TENANTID }}
 9    subscription-id: ${{ vars.VTNSIO_ENTRAAPP_SUBID }}
10#########################################################
11# deploy scripts to azure storage
12#########################################################
13- name: 🚀  Deploy JS Source Maps Azure Storage container
14  run: |
15    az storage blob upload-batch \
16        --overwrite \
17        --auth-mode login \
18        --account-name $STORAGE_ACCOUNT \
19        --source $LOCAL_SOURCE_DIR \
20        --destination $AZURE_STORAGE_CONTAINER_NAME
21  env:
22    STORAGE_ACCOUNT: ${{ vars.VTNSIO_AZCDNSTORAGE }}
23    LOCAL_SOURCE_DIR: assets/js
24    AZURE_STORAGE_CONTAINER_NAME: sourcemaps

This setup has been running smoothly for years without requiring any maintenance of passwords, client secrets, or client certificates. I’ve configured an Entra ID application to trust workflows that run from the main branch of my site’s GitHub repository.

Entra ID app used for Azure resource deployment

Entra ID app used for Azure resource deployment

Federated credentials from trust link with GitHub IdP

This approach is not only easier to manage—since there’s no need to worry about expiring or rotating secrets and certificates—but it’s also more secure because files can only enter the Azure Storage Blob through the automated deployment process!

I apply this same approach with SPFx projects to generate and deploy source maps too!

Want to learn how to auto package & deploy SPFx source maps?

Interested to learn more about how I automate the creation & deployment of source maps? Leave a comment below & I’ll add another article showing how to do that!

Pretty exciting stuff, right?

Previously, implementing federated identities for SPFx CI/CD required significant effort because the CLI for Microsoft 365 lacked native support.

That changed a few weeks ago when I spotted the issue pnp/cli-microsoft365#6610 in the CLI for Microsoft 365’s repository. Within two weeks, it was implemented through pull request pnp/cli-microsoft365/#6611!

Starting with CLI for Microsoft 365 v10.5, you can now set up credential-less CI/CD with GitHub Actions!

Let me show you how to set this up.

Step 1 - Create an Admin Management App

First, create an Entra ID application that SharePoint Admins can use to grant permissions to specific sites for other Entra ID applications. This application needs the Sites.Selected application permission. Let’s call it the SPO_ADMIN_APP.

SPO_ADMIN_APP with Sites.Selected app only Permission

SPO_ADMIN_APP with Sites.Selected app only Permission

This app is used by a SPO tenant admin to grant other apps permission to specific sites rather than the entire tenant.

You’ll use this Entra ID app to grant specific permissions to other Entra ID apps, like the one used in CI/CD scenarios.

This app should be configured with a client certificate. The SharePoint Online tenant admin will use the certificate to connect to your Microsoft 365 tenant.

Step 2 - Create M365 CI/CD App

Next, create another Entra ID application that you’ll use for your CI/CD workflows (you can create one application per workflow or use a single app for multiple workflows). Let’s call this one M365_CICD_APP.

On that app, I’ll configure federated identity by setting it up with my organization (Voitanos), my repository where the project lives, and specifying the entity type as the main branch of my repository. This means Entra ID will return a credential that allows my workflow to authenticate and act as the M365_CICD_APP.

To do this, select Certificates & secrets > Federated credentials and add a new credential:

M365_CICD_APP - Add Federated credential

M365_CICD_APP - Add Federated credential

This app is used by the CI/CD process to deploy the SPPKG to app catalog

Once it’s created, you’ll see it listed on the Certificates & secrets page for the app in the Entra ID portal. Notice the other two tabs, Certificates & Client secrets… nothing is set!

M365_CICD_APP with a single credential, federated with the GitHub IdP

M365_CICD_APP with a single credential, federated with the GitHub IdP

This app is used by the CI/CD process to deploy the SPPKG to app catalog.

With this setup, my workflow will operate under the identity of the Entra ID app M365_CICD_APP.

I want to call out one more thing when you’re setting up the federated identity. Notice the options available to you - one of the entity types you can associate the trust relationship with is a GitHub environment. This means you could use different Entra ID apps to deploy to different environments, like development, test, or production. I use branches for this, but it’s nice having different options.

Step 3 - Grant Permissions to M365 CI/CD App

After creating both apps, you’ll use the SPO_ADMIN_APP to grant permissions to the M365_CICD_APP, which will handle uploading and managing packages in the SharePoint Online app catalog.

 1# sign in to your M365 tenant using the SPO_ADMIN_APP
 2m365 login --authType certificate
 3           --certificateFile ./myCert.pem
 4
 5# grant the M365_CICD_APP write permission
 6#  on the specified site collection
 7m365 spo site apppermission add
 8                --appId $M365_CICD_APP_CLIENTID
 9                --siteUrl $SITE_URL
10                --roles write

With everything set up, I now have my Microsoft Entra ID app, M365_CICD_APP, that I can use to authenticate and write to the tenant app catalog.

Step 4 - Implement the GitHub Workflow

Next, I need to set up my GitHub workflow to obtain the access token by authenticating using federated identity. Thanks to the latest version of the CLI for Microsoft 365, I can now upload packages and deploy them directly to the tenant app catalog.

To implement this, first, grant your GitHub Actions workflow permissions by specifying the ID token and contents.

1permissions:
2  # req: Azure login for user-defined managed identity
3  id-token: write
4  # req: Azure login for user-defined managed identity
5  contents: read

Then, once you’ve installed the CLI for Microsoft 365, you can then use the new support for federated identity by setting the authtype argument on the login command. Make sure to also set the Entra ID client ID and tenant ID for the M365_CICD_APP to use its identity when you log in.

 1- name: Sign in to M365 tenant with federated identity
 2  run: |
 3    m365 login \
 4        --authType ${AUTH_TYPE} \
 5        --clientId ${APP_ID} \
 6        --tenantId ${TENANT_ID}
 7  env:
 8    AUTH_TYPE: federated
 9    APP_ID: ${{ env.M365_CICD_APP_CLIENTID }}
10    TENANT_ID: ${{ env.M365_CICD_APP_TENANTID }}

All that’s left is to handle the deployment! The following workflow job will build the SPFx package, upload it to the specified SharePoint Online tenant app catalog, and deploy it for use, all without storing or having to maintain ANY credentials!

 1name: Production build and deploy
 2
 3defaults:
 4  run:
 5    shell: bash
 6
 7on:
 8  push:
 9    branches:
10      - main
11  workflow_dispatch:
12
13env:
14  # M365_CICD_APP_CLIENT_ID: <repo.variable>
15  # M365_CICD_APP_TENANT_ID: <repo.variable>
16
17permissions:
18  # req: Azure login for user-defined managed identity
19  id-token: write
20  # req: Azure login for user-defined managed identity
21  contents: read
22
23jobs:
24
25  build:
26    name: Production build, bundle, & package
27    runs-on: ubuntu-latest
28    env:
29      SPPKG_FILENAME: "UNDEFINED"
30    outputs:
31      sppkgfilename: ${{ steps.getSppkg.outputs.filename}}
32    steps:
33      # build *.sppkg
34      # ... multiple steps omitted for brevity
35
36      # save *.sppkg for later use & job history
37      - name: 💾  Upload SharePoint package (*.sppkg)
38        uses: actions/upload-artifact@v4
39        with:
40          name: SHAREPOINT_PACKAGE
41          path: sharepoint/solution/${{ env.SPPKG_FILENAME }}
42
43  deploy:
44    name: Deploy to production
45    runs-on: ubuntu-latest
46    needs:
47      - build
48    steps:
49      # download *.sppkg from previous job
50      - name: ⬇️  Download SharePoint package (*.sppkg)
51        uses: actions/download-artifact@v4
52        with:
53          name: SHAREPOINT_PACKAGE
54      # install CLI for M365
55      - name: ⬇️  Install CLI for M365
56        run: |
57          npm install @pnp/cli-microsoft365@10.5.0 -g
58      # signin > M365 tenant w/ Entra ID federated identity
59      - name: 🔑  Sign in to M365 tenant
60        run: |
61          m365 login
62              --authType ${AUTH_TYPE} \
63              --clientId ${APP_ID} \
64              --tenantId ${TENANT_ID}
65        env:
66          AUTH_TYPE: federated
67          APP_ID: ${{ env.M365_CICD_APP_CLIENTID }}
68          TENANT_ID: ${{ env.M365_CICD_APP_TENANTID }}
69      # use CLI for M365 to deploy the package
70      - name: 🚢  Deploy SharePoint package
71        run: |
72          m365 spo app deploy
73              --name ${SPPKG_PATH} \
74              --scope ${SCOPE} \
75              --url ${APP_CATALOG_URL}
76        env:
77          SPPKG_PATH: SHAREPOINT_PACKAGE/${{ needs.build.outputs.sppkgfilename }}
78          SCOPE: sitecollection
79          APP_CATALOG_URL: ${{ env.TARGET_PROD_SITE }}

What about Azure DevOps Pipelines?

Unfortunately, this only works with GitHub Actions at this time. But there’s work ongoing to get this same process working for Azure DevOps Pipelines with the CLI for Microsoft 365. Keep an eye on issue pnp/cli-microsoft365#6649 for their progress!

Share this article

Feedback & comments