I have a hosted Blazor WebAssembly app where I'm trying to pull data from Azure DevOps. We currently use PAT for access but I want people to use their Entra ID account instead.
I have followed the steps here to setup authentication with Entra ID, so I am able to login
I am currently having trouble obtaining a valid access token to call Azure DevOps and retrieve some data.
I keep getting StatusCode: 203, ReasonPhrase: 'Non-Authoritative Information'
If it were 401, I would assume there’s an issue with my app config. However, it’s a 203 so I suspect the token format might be incorrect. The token returned is in JWT format, not the usual PAT format, so I’m unsure how to proceed.
Below is my client app code
Program.cs
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using testauthblazor.Client;
using testauthblazor.Client.Services;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
builder.Services.AddHttpClient("testauthblazor.ServerAPI", client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
builder.Services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("testauthblazor.ServerAPI"));
builder.Services.AddScoped<AzureDevOpsService>();
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("https://twonline.onmicrosoft.com/xxxx-xxxx-xxxx-xxxx-xxxxxxx/.default");
});
await builder.Build().RunAsync();
TestPage.razor
@page "/testpage"
@using Microsoft.AspNetCore.Components.WebAssembly.Authentication
@using testauthblazor.Client.Services
@inject AzureDevOpsService DevOpsService
@inject IAccessTokenProvider TokenProvider
<h3>Azure DevOps Projects</h3>
@if (projects == null)
{
<p>Loading...</p>
}
else if (projects.Count == 0)
{
<p>No projects found.</p>
}
else
{
<ul>
@foreach (var project in projects)
{
<li>@project</li>
}
</ul>
}
@code {
private List<string> projects;
protected override async Task OnInitializedAsync()
{
var result = await TokenProvider.RequestAccessToken();
if (result.TryGetToken(out var token))
{
projects = await DevOpsService.GetProjectsAsync(token.Value);
}
}
}
AzureDevOpsService.cs
public class AzureDevOpsService(HttpClient httpClient)
{
private readonly HttpClient _httpClient = httpClient;
public async Task<List<string>> GetProjectsAsync(string accessToken)
{
var request = new HttpRequestMessage(HttpMethod.Get, "https://dev.azure.com/xyzabc/_apis/projects?api-version=7.1");
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await _httpClient.SendAsync(request);
response.EnsureSuccessStatusCode();
var content = await response.Content.ReadAsStringAsync();
var projects = JsonDocument.Parse(content).RootElement.GetProperty("value").EnumerateArray()
.Select(p => p.GetProperty("name").GetString()).ToList();
return projects;
}
}
The error occurred as you are using wrong scope while generating access token. The correct scope to authenticate Azure DevOps API is 499b84ac-1321-427f-aa17-267ca6975798/.default
Make sure to add vso.project
DevOps API permission in your client application with consent like this:
Now change below code line in your client app's Program.cs file:
builder.Services.AddMsalAuthentication(options =>
{
builder.Configuration.Bind("AzureAd", options.ProviderOptions.Authentication);
options.ProviderOptions.DefaultAccessTokenScopes.Add("499b84ac-1321-427f-aa17-267ca6975798/vso.project");
});
When I run the project again after changing scope value, I got the list of Azure DevOps projects successfully like this:
Reference: