Edited: 4th Feb 2025 to add detail on Azure DevOps Pipelines Service Connection

Introduction

When automating administration tasks via scripts in Azure DevOps in the past I would have commonly used the Azure DevOps REST API. However, today I tend to favour the Azure DevOps CLI. The reason for this is that the CLI wrappers the REST API in such a way that it is easier to use and more consistent.

One of the most noticeable advantages is in the area of authentication. The CLI supports a number of authentication methods that are not directly available to the REST API.

In this post, I will explore the options available to authenticate the Azure DevOps CLI.

Authentication Options

The Azure CLI has a login command az login, but so does the Azure DevOps extension az devops login. We have the option to use either depending on what we are are trying to do.

The key thing to considers is scope, whether you are trying to autenticate with the whole an Azure subscription or just an Azure DevOps instance.

Azure CLI Personal Access Token (PAT)

PATs have historically been the usual means to authenticate with Azure DevOps . They are still perfectly valid, and the easiest way to authenticate for command line usage. However, it is worth noting that Microsoft are now recommending a move away from PATs.

The basic command to use a PAT is az devops login, when this is run you will be prompted for your PAT.

A script can build on this authentication mechanism and allow the PAT to be passed into the script by echoing it into the az command.

echo  "<my-pat>" | az devops login --organization "https://dev.azure.com/myorg

Interactive Login using Entra ID

An alternative to using a PAT is to login to the Azure CLI using your corporate ID that has access to your Azure Subscription. This can be done outside of any script by using the az login command prior to running the script, or making this command the first one in the script.

When the az login command is run, if it can, it will launch a browser allowing you to authenticate via Entra ID using your corporate ID. Once done, your session will be valid for a period of time, and you can use any az commands.

If a browser is not available, the az login command initiates the device code flow and instructs the user to open a browser page at https://aka.ms/devicelogin on another device. Then, enter the code displayed in the terminal. This flow can be forced by using the command

 az login --use-device-code

The problem both those mechanisms that they are not suitable for a script that needs to run unattended, as they requires a user to be present to authenticate.

Using a Service Principle

A Service Principle is an application registration within Entra ID.

In Azure DevOps, a Service Principle can be granted access to an Azure DevOps Organisation and Projects as you would a user account i.e. the Service Account can be granted a license and given permissions inside a project.

You can create a Service Principle using the command

az ad sp create-for-rbac --name myServicePrincipalName1 --role reader --scopes /subscriptions/00000000-0000-0000-0000-000000000000 

Note: The Service Principle must have at least the role of ‘reader’ in the Azure Subscription associated with the Entra ID

When the above command is run, the result will be block of JSON including an appId, password and tenantID. These values should be stored securely

Once the Service Principle has been created you can grant it permissions to the Azure DevOps Organisation and Projects as needed.

Finally, using the securely stored values, you can now authenticate with the Azure ClI using the form

az login --service-principal -u <appID> -p <password> --tenant <tenantid>

Unlike the interactive login, if you use this mechanism you have the option to pass these values into a script as parameters. So can be used for unattended scripts.

The issue with this mechanism is that the password will expire, and has to be renewed. So there is a maintainance overhead.

Managed Identity

The final option is to use an Azure Managed Identity.

A Managed Identity is created as a resource in Azure. Like a Service Principle, a Managed Identity can be granted a license and permissions in Azure DevOps.

To login with a Managed Identity on an Azure hosted VM you can use the command

az login --identity

This works because the Managed Identity must be associated with the Azure VM you are running the az command on.

The need to be on an Azure resource is the limitation of Managed Identities. So are not appropriate to your needs if you wish to run your script, that uses the Azure CLI, from locations outside Azure.

So what do I use?

My most common use-case is to run a script that uses the AZ CLI from within an Azure DevOps Pipeline. This requirement gives need to two ways of working.

When I need to access an Azure Subscription

If my script needs permissions beyond Azure DevOps, interacting with a range of Azure resource in a subscription. I could create a Service Principle and grant it permissions to the required resources in Azure, and then pass the Service Principle values into the script as parameters from secret Azure DevOps Pipeline variables.

However, that is a lot a work, and there is a better option built into Azure DevOps, Service Connections. When you add an ‘Azure Resource Manager’ service connection you are able to pick from a variety of options such as App Registration (Service Principle), or Managed Identity. Most of the options availble automatically create the required Azure resources required and can support workload identity federation so you don’t have to worry over expiring secrets.

You can then very easily run your scripts on a pre authenticated PowerShell session using the AzurePowerShell task

- task: AzurePowerShell@5
  displayName: Run a Script against Azure Resources
  inputs:
    azureSubscription: 'MyServiceConnection'
    azurePowerShellVersion: 'LatestVersion'
    ScriptType: 'InlineScript'
    Inline: |
      # your az commands, not need to login as it is already done
      az ....      
    FailOnStandardError: true
    pwsh: true

When I only need to access Azure DevOps

However, my scripts commonly only need to interact with Azure DevOps, and in this case I just use the Build Agent token as a PAT. This is because it avoids the need to set anything up, you can easily pass the token into the script as an environment variable.

trigger: none

pool:
  vmImage: ubuntu-latest

steps:
- task: PowerShell@2
  displayName: 'Increment PBI count'
  inputs:
    targetType: 'inline'
    pwsh: true
    script: |   
      # I don't need to call az login as it done automaticallly
      az devops ....
  env:
    AZURE_DEVOPS_EXT_PAT: $(System.AccessToken)