Search code examples
c#azure-active-directoryaccess-tokenazure-devops-rest-api

Execution works with personal access token, but not using AAD access token for Azure DevOps


I have my below code which output the master branch stats in JSON format from Azure DevOps repository and I am capturing the required output. This works when I use the personal access token the authentication works and gets back with the results from the API.

But when I try to generate Access token using the registered app in AAD(has delegated user impersonation enabled for Azure DevOps under API permissions), I am able to generate the access token and then passing it while calling the API, but it returns back with

StatusCode: 203, ReasonPhrase: 'Non-Authoritative Information', Version: 1.1, Content: System.Net.Http.StreamContent

public static async Task GetBuilds()
{
    string url = "Azure Dev-Ops API";
    var personalaccesstoken = "personalaccesscode";
    //var personalaccesstoken = token.GetYourTokenWithClientCredentialsFlow().Result;
    string value = null;

using (HttpClient client = new HttpClient())
{
    client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", Convert.ToBase64String(ASCIIEncoding.ASCII.GetBytes(string.Format("{0}:{1}", "", personalaccesstoken))));
    using (HttpResponseMessage response = await client.GetAsync(url))
    {
        response.EnsureSuccessStatusCode();
        string responseBody = await response.Content.ReadAsStringAsync();
        dynamic jsonObject = JsonConvert.DeserializeObject(responseBody);
        value = jsonObject;
    }
}

if (value != null)
{
    Console.WriteLine(value);
}
}

public static async Task<string> GetYourTokenWithClientCredentialsFlow()
{
    string tokenUrl = $"https://login.microsoftonline.com/{tenant ID}/oauth2/token";
    var tokenRequest = new HttpRequestMessage(HttpMethod.Post, tokenUrl);

    tokenRequest.Content = new FormUrlEncodedContent(new Dictionary<string, string>
    {
        ["grant_type"] = "client_credentials",
        ["client_id"] = "client ID",
        ["client_secret"] = "client secret",
        ["resource"] = "https://graph.microsoft.com/"
    });

    dynamic json;
    dynamic token;
    string accessToken;

    HttpClient client = new HttpClient();

    var tokenResponse = client.SendAsync(tokenRequest).Result;
    json = await tokenResponse.Content.ReadAsStringAsync();
    token = JsonConvert.DeserializeObject(json);
    accessToken = token.access_token;
    return accessToken;
}

Tried to test using postman using the access token generated using above code and get as below screenshot.

enter image description here

what I am doing wrong here and how can I fix the problem?


Solution

  • The azure ad access token is a bearer token. You do not need to use it as basic auth.

    Try with the following code:

    client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", GetYourTokenWithClientCredentialsFlow().Result);
    

    Update:

    1. Register a new app enter image description here

    2. Set the app as a public client by default enter image description here

    3. Add permission to DevOps API enter image description here

    4. Create a new project, install Microsoft.IdentityModel.Clients.ActiveDirectory package enter image description here

    5. Code sample

    class Program
    {
        static string azureDevOpsOrganizationUrl = "https://dev.azure.com/jack0503/"; //change to the URL of your Azure DevOps account; NOTE: This must use HTTPS
        static string clientId = "0a1f****-****-****-****-a2a4****7f69";          //change to your app registration's Application ID
        static string replyUri = "https://localhost/";                     //change to your app registration's reply URI
        static string azureDevOpsResourceId = "499b84ac-1321-427f-aa17-267ca6975798"; //Constant value to target Azure DevOps. Do not change  
        static string tenant = "hanxia.onmicrosoft.com";     //your tenant ID or Name
        static String GetTokenInteractively()
        {
            AuthenticationContext ctx = new AuthenticationContext("https://login.microsoftonline.com/" + tenant); ;
            IPlatformParameters promptBehavior = new PlatformParameters(PromptBehavior.Auto | PromptBehavior.SelectAccount);
            AuthenticationResult result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, new Uri(replyUri), promptBehavior).Result;
            return result.AccessToken;
        }
    
        static String GetToken()
        {
            AuthenticationContext ctx = new AuthenticationContext("https://login.microsoftonline.com/" + tenant); ;
            UserPasswordCredential upc = new UserPasswordCredential("jack@hanxia.onmicrosoft.com", "yourpassword");
            AuthenticationResult result = ctx.AcquireTokenAsync(azureDevOpsResourceId, clientId, upc).Result;
            return result.AccessToken;
        }
        static void Main(string[] args)
        {
            //string token = GetTokenInteractively();
            string token = GetToken();
    
            using (var client = new HttpClient())
            {
                client.BaseAddress = new Uri(azureDevOpsOrganizationUrl);
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
                client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
    
                HttpResponseMessage response = client.GetAsync("_apis/projects").Result;
    
                if (response.IsSuccessStatusCode)
                {
                    Console.WriteLine("\tSuccesful REST call");
                    var result = response.Content.ReadAsStringAsync().Result;
                    Console.WriteLine(result);
                }
                else if (response.StatusCode == System.Net.HttpStatusCode.Unauthorized)
                {
                    throw new UnauthorizedAccessException();
                }
                else
                {
                    Console.WriteLine("{0}:{1}", response.StatusCode, response.ReasonPhrase);
                }
    
                Console.ReadLine();
            }
        }
    }