.NET 8 DownstreamAPI not authenticating

52 views Asked by At

I have protected my .NET 8 Blazor app using Microsoft Identity. In my application I want to fetch data from my API which is also protected using Microsoft Identity. I use the DownstreamAPI to send a request on behalf of the user. I followed a tutorial from Microsoft to setup my application. This tutorial is based on .NET 7, and there it works fine so my setup seems to be right.

Program.cs

JwtSecurityTokenHandler.DefaultMapInboundClaims = false;

builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi(
        new string[] {
            builder.Configuration.GetSection("DownstreamApi:Scopes:Read").Get<string>()!,
            builder.Configuration.GetSection("DownstreamApi:Scopes:Write").Get<string>()!
        }
    )
    .AddDownstreamApi("DownstreamApi", builder.Configuration.GetSection("DownstreamApi"))
    .AddInMemoryTokenCaches();

builder.Services.AddControllersWithViews()
        .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy
    options.FallbackPolicy = options.DefaultPolicy;
});

// Add services to the container.
builder.Services.AddRazorPages();

Appsettings.json

{
  "AzureAd": {
    "Authority": "https://login.microsoftonline.com/<DIRECTORY>/",
    "ClientId": "<CLIENT_ID>",
    "CallbackPath": "/signin-oidc",
    "SignedOutCallbackPath ": "/signout-oidc",
    "ClientCredentials": [
      {
        "SourceType": "ClientSecret",
        "ClientSecret": "<CLIENT_SECRET>"
      }
    ]
  },
  "DownstreamApi": {
    "Scopes": {
      "Read": "api://<CLIENT_ID>/ToDoList.Read",
      "Write": "api://<CLIENT_ID>/ToDoList.ReadWrite"
    },
    "BaseUrl": "https://localhost:7281"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*"
}

Sending request


@inject IDownstreamApi DownstreamApi;

...

@code
{
    ...
    private async Task GetFromApi()
    {
        try
        {
            Console.WriteLine("Getting token");
            var auth = await DownstreamApi.CallApiForUserAsync(
            ServiceName,
            options => options.RelativePath = "/api/authTest");
            Console.WriteLine(auth.StatusCode);
            Console.WriteLine(auth.Content.ReadAsStringAsync().Result);
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    }
}

The problem is that the bearer token it is sending to the API does not seem to be right. When I destruct the bearer tokens using jwt.io these are the results:

.NET 7 (Working token):

enter image description here

.NET 8 (Invalid token)

enter image description here

I can't seem to find out what I am missing or doing wrong.

2

There are 2 answers

0
Tiny Wang On

Just like what you mentioned, the token looks to be the incorrect token.

I had codes below to generate 2 tokens, let's see the differences.

var token = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "User.Read" });
var token2 = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "api://client_id/Tiny.Greet" });

enter image description here

I used ITokenAcquisition service to generate access token we required to call external APIs which is also secured by AAD, the first token is used to call MS Graph API, the second token is for my custom API.

Since you didn't share any codes to prove what kind of Blazor App you are having, so that I can only share all my related codes and settigns. What I used is a blazor web app .net 8 which is converted from blazor server project .net 7. You can take a look at this answer to see what I changed in the migration.

using BlazorServer7To8.Data;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
using BlazorServer7To8;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.Extensions.Configuration;

var builder = WebApplication.CreateBuilder(args);

// Add services to the container.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApp(builder.Configuration.GetSection("AzureAd"))
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDistributedTokenCaches();

builder.Services.AddControllersWithViews()
    .AddMicrosoftIdentityUI();

builder.Services.AddAuthorization(options =>
{
    // By default, all incoming requests will be authorized according to the default policy
    options.FallbackPolicy = options.DefaultPolicy;
});
builder.Services.AddRazorPages();
//builder.Services.AddServerSideBlazor()
//    .AddMicrosoftIdentityConsentHandler();
builder.Services.AddRazorComponents()
    .AddInteractiveServerComponents()
    .AddMicrosoftIdentityConsentHandler();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<WeatherForecastService>();

In the Index.razor, inject ITokenAcquisition service to generate access token, because we already have EnableTokenAcquisitionToCallDownstreamApi in Program.cs.

@page "/"
@using Microsoft.Identity.Web
@inject Microsoft.Identity.Web.ITokenAcquisition TokenAcquisitionService

<PageTitle>Index</PageTitle>

<h1>Hello, world!</h1>

Welcome to your new app.

<SurveyPrompt Title="How is Blazor working for you?" />

@code{
    protected override async Task OnInitializedAsync()
    {
        var token = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "User.Read" });
        var token2 = await TokenAcquisitionService.GetAccessTokenForUserAsync(new string[] { "api://client_id/Tiny.Greet" });
        var a = 1;
    }
}

Although I had downstream API settings with Graph API, it doesn't affect me to use On_behalf_flow to generate another access token for other scopes.

"AzureAd": {
  "Instance": "https://login.microsoftonline.com/",
  "Domain": "tenant_Id",
  "TenantId": "tenant_Id",
  "ClientId": "client_Id",
  "CallbackPath": "/signin-oidc",
  "ClientSecret": "client_secret"
},
"DownstreamApi": {
  "BaseUrl": "https://graph.microsoft.com/v1.0",
  "Scopes": "User.ReadWrite.All"
}

I trust a correct access token can help solve your error. Here's my test result for the token generation.

enter image description here

0
TikTakToe On

Try changing your scopes to an array as per the 'docs':

https://github.com/AzureAD/microsoft-identity-web/blob/master/docs/blog-posts/downstreamwebapi-to-downstreamapi.md

so

  "DownstreamApi": {
"Scopes": {
  "Read": "api://<CLIENT_ID>/ToDoList.Read",
  "Write": "api://<CLIENT_ID>/ToDoList.ReadWrite"
},
"BaseUrl": "https://localhost:7281"

},

becomes:

  "DownstreamApi": {
"Scopes": [
   "api://<CLIENT_ID>/ToDoList.Read",
  "api://<CLIENT_ID>/ToDoList.ReadWrite"
],
"BaseUrl": "https://localhost:7281"

},