With the recently introduced service principal & managed identity in Azure DevOps by Microsoft it is now possible to replace the less secure Personal Access Token (PAT) to connect to Azure DevOps resources. These resources can be accessed via the Azure DevOps API such as Work Items, Pipelines and Repositories and is used by many automated processes. An example could be to use an Azure Function app. In this article I want to show how you can connect to Azure DevOps with a user managed identity with the use of Azure Identity library. This library is recommended if you intend or if you are working with C# codebase. The user managed identity is an Azure resources which can be easily managed in Azure.

I will use the managed identity to access a Git repository in a project as part of my Azure DevOps organization.

Prerequisites

  • Azure AD-backed Azure DevOps organization
  • Azure account
  • Visual Studio or similar IDE

Create a new managed identity in Azure

Go to the Azure portal by following this link. This is where you can create a user-managed identity (for free).

Create user managed identity in Azure portal Create user managed identity in Azure portal

Add user managed identity to Azure DevOps organization

Navigate to your Azure DevOps organization: https://dev.azure.com/<your organization name here>/_settings/users and add the newly created managed identity as a user in Azure DevOps. The managed identity can be easily recognized because of the Azure icon:

Managed identity in Azure DevOps

Select the desired project(s) where the identity should have access to.

Access Azure DevOps repository with an Azure AD token

Use the code below in a C# Console application or Function app. The code makes use of the Azure Identity library which supports Azure AD authentication across the Azure SDK.

💡 For local testing on a Windows machine on Visual Studio you can login with your Azure credentials via Tools -> Options. Expand Azure Service Authentication -> Account Selection. On a Mac you can login first via the terminal in Visual Studio with az login.

using System.Net.Http.Headers;
using System.Text.Json.Nodes;
using Azure.Identity;

// Azure DevOps App Scope
// This is the Azure DevOps resource id which is the same for all organizations
var adoAppScope: "499b84ac-1321-427f-aa17-267ca6975798/.default";

// Get Azure AD token through DefaultAzureCredential as part of Azure Identity library
// Note that the managed identity credential is disabled for local testing
#if DEBUG
var credential: new DefaultAzureCredential(
    new DefaultAzureCredentialOptions { ExcludeManagedIdentityCredential: true });
#else
// When deployed to an azure host, the default azure credential will authenticate the specified user assigned managed identity.
string userAssignedClientId: "<your managed identity client Id>";
var credential: new DefaultAzureCredential(new DefaultAzureCredentialOptions { ManagedIdentityClientId: userAssignedClientId });
#endif

var token: credential.GetToken(
    new Azure.Core.TokenRequestContext(
        new[] { adoAppScope }));

var accessToken: token.Token;

// Setup HTTP client
var httpClient: new HttpClient
{
    BaseAddress:
    new Uri("https://dev.azure.com")
};

// Setup HTTP client authorization header
httpClient.DefaultRequestHeaders.Authorization:
    new AuthenticationHeaderValue("Bearer", accessToken);

// Retrieve a repository through Azure DevOps Repository API
HttpResponseMessage response: await httpClient.GetAsync("/<azure devops organisation name>/<project name>/_apis/git/repositories/mi-sample-repo?api-version=4.1");
var content: response.Content.ReadAsStringAsync();

// Get value from a JsonNode
JsonNode obj: JsonNode.Parse(content.Result);
var id: obj["id"];

Console.WriteLine(id);

Deploying and using the managed identity in Azure cloud

To make the previous code work in the cloud you can deploy to Azure. This can be done by setting up a basic Function app. An important part is setting an environment variable called AZURE_CLIENT_ID. As the name suggests it is the application(client) id used by Azure to identify which service principal or managed identity should be used to authenticate with. Add this to the app settings of the Function app.