In this article, we’ll show you how to connect to Azure DevOps (ADO) with C# using a Personal Access Token (PAT). A Personal Access Token acts like a secure key that allows your application to connect to ADO without exposing your username and password. A PAT is safer than basic authentication because a PAT can be scoped to minimum permissions and revoked if leaked.
Protect your token
Although a personal access token is safer than a username and password, you should never hard-code your PAT in your source code. You should never commit your PAT into Git or any source-code control. Be just as careful with your PAT as you would a password.
Establishing a connection
This example uses the Azure DevOps SDK, which is the recommended approach unless you have specific reasons for calling the REST APIs directly. To establish a connection with the SDK, you create an instance of VssBasicCredential
and pass it to VssConnection
.
VssBasicCredential
normally takes a username and password. You can use this same class to send a Personal Access Token instead. To do this, you leave the username blank, and you set the password to the token value.
Here is the core code:
// VssBasicCredential takes a username + password.
// In the case of PAT authentication, username is left empty,
// and PAT is provided as the "password".
var credentials = new VssBasicCredential(string.Empty, "your pat string");
// Create a connection to Azure DevOps using the URL + credentials
var connection = new VssConnection(new Uri("your org URL"), credentials);
// Get a client for the Work Item Tracking service
// This client is used to query, read, and update work items
var witClient = connection.GetClient<WorkItemTrackingHttpClient>();
Instruction for a quick example app
This example is written as a console app that connects to Azure DevOps with the Azure DevOps (formerly Team Foundation Services) SDK. You will need Visual Studio with the .NET SDK installed.
- Create a new console app.
- Install required NuGet packages:
Install-Package Microsoft.TeamFoundationServer.Client
- Copy and paste the code below into your main file
The code below contains a working example of connecting to Azure DevOps and performing a query. You will need to update a few lines with your project name, organization URL and personal access token — see the comments. In a real app you would pull these values from a configuration file or user settings.
- Generate a personal access token
See How to generate a personal access token in Azure DevOps.
- Paste the token into the designated line
Remember, this is just a demonstration. For a real app, you should never store a personal access token in source code.
- Review the source code comments and run the code
- If the query does not return anything, check the work item type
The example query is designed to work with the default Basic template in Azure DevOps. If the query doesn’t return any records, you may need to change the query to reflect your particular template. Identify a status that your work items have, e.g., Completed, and use that instead of “To Do”.
Code
// ----------------------------
// Required Namespaces
// ----------------------------
// Azure DevOps (formerly TFS) client SDK for accessing
// Work Item Tracking (WIT) REST APIs
// NuGet: Microsoft.TeamFoundationServer.ExtendedClient
using Microsoft.TeamFoundation.WorkItemTracking.WebApi;
// Models for work items (e.g., WorkItem, Wiql, WorkItemQueryResult)
// NuGet: Microsoft.TeamFoundationServer.ExtendedClient
using Microsoft.TeamFoundation.WorkItemTracking.WebApi.Models;
// Provides credentials classes (like VssBasicCredential) for
// authenticating with Azure DevOps
// NuGet: Microsoft.VisualStudio.Services.Client
using Microsoft.VisualStudio.Services.Common;
// Provides the VssConnection class, which represents a connection
// to an Azure DevOps organization
// NuGet: Microsoft.VisualStudio.Services.Client
using Microsoft.VisualStudio.Services.WebApi;
// Standard .NET namespaces
using System;
using System.Linq; // for LINQ (used in .Select and .Any)
using System.Threading.Tasks; // for async/await support
namespace DevopsDemo
{
class Program
{
// Main method: entry point of the console app
// "async Task Main" allows us to use "await" inside Main
static async Task Main(string[] args)
{
// ------------------------------------
// 1. Define connection details
// ------------------------------------
// Organization URL
string url = "https://dev.azure.com/your_organization";
// Personal Access Token (PAT) generated from Azure DevOps
string pat = "your_personal_access_token";
// Project name inside Azure DevOps
string project = "your_project";
// ------------------------------------
// 2. Authenticate using PAT
// ------------------------------------
// VssBasicCredential takes a username + password.
// In the case of PAT authentication, username is left empty,
// and PAT is provided as the "password".
var credentials = new VssBasicCredential(string.Empty, pat);
// Create a connection to Azure DevOps using the URL + credentials
var connection = new VssConnection(new Uri(url), credentials);
// Get a client for the Work Item Tracking service
// This client is used to query, read, and update work items
var witClient = connection.GetClient<WorkItemTrackingHttpClient>();
// ------------------------------------
// 3. Define a WIQL query
// ------------------------------------
// WIQL = Work Item Query Language (SQL-like syntax to query
// work items in Azure DevOps)
Wiql wiql = new Wiql()
{
// This query selects all work items in the given project
// where the State = "To Do".
// We are requesting only Id and Title fields.
Query = $"Select [System.Id], [System.Title] " +
$"From WorkItems " +
$"Where [System.TeamProject] = '{project}' " +
$"AND [System.State] = 'To Do'"
};
// ------------------------------------
// 4. Execute the WIQL query
// ------------------------------------
// This returns references (IDs + URLs) to the work
// items that match the query.
var result = await witClient.QueryByWiqlAsync(wiql);
// ------------------------------------
// 5. If work items are found, fetch details
// ------------------------------------
if (result.WorkItems.Any())
{
// Extract the work item IDs from the query result
var ids = result.WorkItems.Select(wi => wi.Id).ToArray();
// Get full work item details (fields, relations, etc.)
// WorkItemExpand.Fields tells the client to include field data
var workItems = await witClient.GetWorkItemsAsync(ids, expand: WorkItemExpand.Fields);
// ------------------------------------
// 6. Print work item details
// ------------------------------------
foreach (var wi in workItems)
{
// Print the unique ID
Console.WriteLine($"Work Item Id: {wi.Id}");
// Print the title field
Console.WriteLine($"Title : {wi.Fields["System.Title"]}");
// Separator line
Console.WriteLine(new string('-', 40));
}
}
else
{
// No results found
Console.WriteLine("No work items found.");
}
}
}
}
Leave a Reply