Skip to content

Add login through federated identity for Azure Devops #6649

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
martinlingstuyl opened this issue Mar 12, 2025 · 4 comments · May be fixed by #6670
Open

Add login through federated identity for Azure Devops #6649

martinlingstuyl opened this issue Mar 12, 2025 · 4 comments · May be fixed by #6670

Comments

@martinlingstuyl
Copy link
Contributor

martinlingstuyl commented Mar 12, 2025

Implementing Federated Identity when running in Azure DevOps should be added to the CLI so we can start supporting deployment of apps without adding certificates to CI/CD.

Implementation

I've discovered that an environment variable called SYSTEM_OIDCREQUESTURI is available in Azure DevOps. Based on that I found this documentation.

Some researching and try-outs resulted in following specs.

We can start supporting m365 login --authType federatedIdentity within Azure DevOps. This can be done as follows.

We can implement two roads at the same time:

  1. Not using a service connection
  2. Using a Service Connection

Road 1: Not using a Service Connection

It's possible to not use a service connection. When not using a service connection you need to specify the following yaml in your build pipeline:

- task: PowerShell@2
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)  
  inputs:
    targetType: 'inline'
    script: |
      m365 login --authType federatedIdentity --appId "<some-appid-or-variable>" --tenant "<some-tenantid-or-variable>"

How it works:

We're not running in a service connection, so we need to input the appId and tenantId.
We also need to add the env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) section, to make sure the SYSTEM_ACCESSTOKEN environment variable is available. That token is necessary to call the local OIDC endpoint.

The m365 login sequence will check if SYSTEM_OIDCREQUESTURI and SYSTEM_ACCESSTOKEN are available. It will start signing in. It will request a federation token from the URL <oidcRequestUrl>?api-version=7.1. This will result in a token that can be passed to Entra ID to be swapped for an access token. The appId and tenant values from the command options will be used for retrieving the token.

The specified app will need a federated identity credential with the right Subject and Issuer. These values are normally determined when you create a Service Connection, but in this case you'll need to configure them manually. The subject will look like this: p://contoso/MyProjectName/MyRepoName. The issuer will be https://vstoken.dev.azure.com/<some-devops-organization-id>. The organization id can be determined by exporting the organizations from DevOps:

Image

Road 2: Using a Service Connection

When using a service connection you need to specify the following yaml in your build pipeline:

- task: AzurePowerShell@5 
  env:
    SYSTEM_ACCESSTOKEN: $(System.AccessToken)
  inputs:
    azureSubscription: 'MyServiceConnection'
    ScriptType: 'InlineScript'
    azurePowerShellVersion: LatestVersion
    Inline: |
      m365 login --authType federatedIdentity

How it works:

When using a service connection we need the AzurePowerShell@5 task as a wrapper task so that we can input the service connection. The task will make the following environment variables available:

  • AZURESUBSCRIPTION_SERVICE_CONNECTION_ID
  • AZURESUBSCRIPTION_CLIENT_ID
  • AZURESUBSCRIPTION_TENANT_ID

We also need to add the env: SYSTEM_ACCESSTOKEN: $(System.AccessToken) section, to make sure the SYSTEM_ACCESSTOKEN environment variable is available. That token is necessary to call the local OIDC endpoint.

The m365 login sequence will check if SYSTEM_OIDCREQUESTURI and SYSTEM_ACCESSTOKEN are available. It will start signing in. It will request a federation token from the URL <oidcRequestUrl>?api-version=7.1&serviceConnectionId=<serviceConnectionId. This will result in a token that can be passed to Entra ID to be swapped for an access token. The App AZURESUBSCRIPTION_CLIENT_ID on the tenant AZURESUBSCRIPTION_TENANT_ID will be used for retrieving the token.

The specified app will need a federated identity credential with the right Subject and Issuer. These values are determined when you create the Service Connection. The subject will look like this: sc://contoso/MyProjectName/MyServiceConnectionName.

Earlier specs idea 2: create a devops extension task and add a federatedToken option to the login command

This idea is superseded by the latest idea because we'd rather avoid having to force devs to use an extension task. We'd like to use just the m365 login command if possible. Also it takes another repo to manage.

The point with Azure DevOps is that we need to use service connections. It seems though that service connections are not just available within any task. You need to build a task extension to be able to leverage the azure-pipelines-task-lib to get at the service connection.

Studying the AzureCLIV2 task and how it interacts with the actual az cli, I believe I know how we can tackle this challenge. We can do it in the exact same way as the az cli:

  1. We'll need to build an Azure DevOps extension (just like we have a GitHub extension) that is able to get at the service connections and can retrieve a federated token. This extension installs the CLI, retrieves the federated token and then calls the login command, giving the federated token as an option.
  2. We'll add a --federatedToken [federatedToken] option to the login command. The login command calls login.microsoftonline.com and swaps the federated token for an access token.

And that should be it! I believe I have all these steps figured out now. But of course, I'm interested to hear your opinions. I've already created a repository and uploaded some code for the extension.

We'll need to publish the extension to the DevOps marketplace when it's finished.

As a side-thing, the extension could be made in such a way that it can also deal with other types of service connections, like one with a secret, or one with a certificate.

The CLI option should be like:

Option

Option Description
-t, --federatedToken [federatedToken] Federated token that can be used for OIDC token exchange. Can be used together with --authType federatedIdentity when running the CLI in Azure DevOps.
Earlier specs idea 1: pass the service connection to the CLI

This option is impossible. You need a DevOps extension task to pass a service connection into and load data from the azure devops task library.

Options

We'll allow logging in through the existing option --authType federatedIdentity (see #6610)

But Azure DevOps needs to know what Service Connection will be used, so we'll need an additional option:

Option Description
-c, --serviceConnection [serviceConnection] The Azure DevOps service connection to use. Can only used in Azure DevOps when logging in with --authType federatedIdentity

Implementation

For inspiration, we'll need to check how the az cli does it: check out the source code here

Just like with GitHub it first retrieves an idToken. That will probably get posted to Entra Id to switch it for an access token. The rest will need to be researched yet.

We may need to install azure-pipelines-task-lib. For example for retrieving variables that we need.
Let's check out if we really need it.

Requirements

  • If the service connection is not of the Workload Identity Federation type, logging in should fail with a clear exception message.
@martinlingstuyl
Copy link
Contributor Author

martinlingstuyl commented Mar 28, 2025

The point with Azure DevOps is that we need to use service connections. It seems though that service connections are not just available within any task. You need to build a task extension to be able to leverage the azure-pipelines-task-lib to get at the service connection.

Studying the AzureCLIV2 task and how it interacts with the actual az cli, I believe I know how we can tackle this challenge. We can do it in the exact same way as the az cli:

  1. We'll need to build an Azure DevOps extension (just like we have a GitHub extension) that is able to get at the service connections and can retrieve a federated token. This extension installs the CLI, retrieves the federated token and then calls the login command, giving the federated token as an option.
  2. We'll add a --federatedToken [federatedToken] option to the login command. The login command calls login.microsoftonline.com and swaps the federated token for an access token.

And that should be it! I believe I have all these steps figured out now. But of course, I'm interested to hear your opinions. I've already created a repository and uploaded some code for the extension.

We'll need to publish the extension to the DevOps marketplace when it's finished.

As a side-thing, the extension could be made in such a way that it can also deal with other types of service connections, like one with a secret, or one with a certificate.

The CLI option should be like:

Option

Option Description
-t, --federatedToken [federatedToken] Federated token that can be used for OIDC token exchange. Can be used together with --authType federatedIdentity when running the CLI in Azure DevOps.

@pnp/cli-for-microsoft-365-maintainers

@martinlingstuyl
Copy link
Contributor Author

I find another route using Environment variables:

https://learn.microsoft.com/en-us/azure/devops/release-notes/2024/sprint-240-update#pipelines-and-tasks-populate-variables-to-customize-workload-identity-federation-authentication

Will be digging into this more. This will help us to build it into the CLI itself, without having to create an extension task for the CLI in the first place.

martinlingstuyl added a commit to martinlingstuyl/cli-microsoft365 that referenced this issue Apr 3, 2025

Verified

This commit was signed with the committer’s verified signature.
martinlingstuyl Martin Lingstuyl
@martinlingstuyl
Copy link
Contributor Author

OK @pnp/cli-for-microsoft-365-maintainers , I've got a working setup with AND without service connections. Very nice, even if I say so myself 😉

It can be tested using my published package: @martinlingstuyl/cli-microsoft365@next

- task: Npm@1
  inputs:
    command: 'custom'
    customCommand: 'install @martinlingstuyl/cli-microsoft365@next -g'

Check out the associated PR for the code changes.

@waldekmastykarz
Copy link
Member

Very cool @martinlingstuyl 👏

martinlingstuyl added a commit to martinlingstuyl/cli-microsoft365 that referenced this issue Apr 24, 2025

Verified

This commit was signed with the committer’s verified signature.
martinlingstuyl Martin Lingstuyl
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
2 participants