3

I'm trying to build a new WebApi secured using access tokens from azure active directory.

I'm using .net core v3.1 and visual studio 2019.

I created a new project using the "Asp.net core web application" template and picked an "API" project and changed the authentication type to "Work or School Accounts" and set the App ID Url to Api://Automation/TestApi

Visual Studio then scaffolded me a web API with a mock weather forecast service which, if I comment out the [Authorize] attribute spins up nicely in a browser as expected. It also created me an Application Registration in AzureActive Directory.

With the [Authorize] attribute back in place I had trouble calling the API from a client app so I decided to call the API using postman to see what's going on.

I added a client secret to the app registration visual studio created and put a postman request together as below using the Application (client) Id and api://Automation/TestApi/.default as the scope.

Postman Call to token endpoint

This works fine and returns an access token however when I try to use that access token to call the default weatherforcast endpoint I get an HTTP 401 unauthorized error with the following in the WWW-Authenticate response header

"Bearer error="invalid_token", error_description="The audience 'api://Automation/TestApi' is invalid"

Is there something I'm missing? I cannot find any clue as to what the audience is expected to be and no obvious way of controlling that.

As Requested here is the content of the expose an API screen

Expose an API

and the decoded jwt token I am using

enter image description here

Update

I tried out @CarlZhao s answer below and it didn't really work. However I remembered a question I asked a while ago about the wrong issuer in the token the outcome from this is to manually edit the manifest json in the registration for the API and set "accessTokenAcceptedVersion": 2

Now I get a v2 function with the clientId guid as the audience

V2 JWT

However, using this token still doesn't work!! I now get an error about the issuer:

Bearer error="invalid_token", error_description="The issuer 'https://login.microsoftonline.com/{{guid}}/v2.0' is invalid

Twisted
  • 2,939
  • 4
  • 32
  • 54
  • I need you to provide two screenshots: 1. Go to AAD portal> App registrations>your app>api app>Expose an API. 2. Use https://jwt.ms/ to parse your access token and provide screenshots. – Carl Zhao Oct 30 '20 at 02:23
  • @CarlZhao screen shots attached – Twisted Oct 30 '20 at 09:07

3 Answers3

1

You missed some important steps, your access token is also wrong, it lacks the necessary permissions. You can try to follow my method:

You need to create 2 applications, one representing the client application and the other representing the api application, and then use the client application to call the Web api application.

First, you need to expose the api of the application representing the web api, you can configure it according to the following process:

Azure portal>App registrations>Expose an API>Add a scope>Add a client application

Because you are using the client credential flow, next, you need to define the manifest of api applications and grant application permissions to your client applications (this is the role permissions you define yourself, you can find it in My APIs when you add permissions).Then you need to click the admin consent button to grant administrator consent for this permission.

This is the process of defining the manifest.

enter image description here

This is to grant permissions for the client application:

enter image description here

Finally, you can request a token for your api application:

enter image description here

Parse the token and you will see:

enter image description here

Carl Zhao
  • 8,543
  • 2
  • 11
  • 19
  • Thanks Carl, I will give this a try now, I did notice that your access token still has the same aud value as mine so I would expect it to be also rejected for the same reason. I've also noticed that we are both getting ver "1.0" tokens from the V2 endpoint which looks suspicious to me. – Twisted Oct 30 '20 at 11:44
  • I followed the steps through, including the manifest step which I had missed on my previous attempts but unfortunately, it didn't work - same error as before, Could you just clarify that in your answer Test1 is the app registration for the client and Test2 is the API and when you request a token you are passing the clientId from the Client not the API? – Twisted Oct 30 '20 at 13:33
0

I've made some further progress which I'm going to use to provide my own answer.

The generated code is using the Microsoft.AspNetCore.Authentication.AzureAD.UI package v 3.1.x to validate the token.

using Fiddler I was able to see that it is using the older endpoints instead of the current "V2.0" endpoints.

the significance of this is that the token version dictates which audience and issuer are returned within the token.

In a V1 token, the audience is the "Application Id Url" from the application registration and the issuer is "https://sts.windows.net/{tennantId}"

in a v2 token, the audience is the Client ID (GUID) of the application registration and the issuer is https://login.microsoftonline.com/{tenantId}/V2.0.

The underlying problem is that this library is expecting some kind of hybrid token with the audience value of a v2.0 token (eg the client Id) but from the v1 issuer https://sts.windows.net/.

I couldn't find a way to make it look at the v2.0 endpoints for configuration but I found a number of old issues on git hub with similar issues which have been closed without resolution.

In particular

My workaround is to set "accessTokenAcceptedVersion": 2 in the application registration "manifest" json on the app registration. This ensures that the correct audience is in the token.

The second step is to add custom issuer verification in the ConfigureServices method of the startup class. Here's my code

  AzureADOptions opt = new AzureADOptions();
  Configuration.Bind("AzureAd", opt);
  string expectedIssuer = $"{opt.Instance}{opt.TenantId}/v2.0";
  services.Configure<JwtBearerOptions>(AzureADDefaults.JwtBearerAuthenticationScheme, options =>
        {
            options.TokenValidationParameters.IssuerValidator = (issuer, securityToken, validationParameters) =>
            {
                if (issuer == expectedIssuer) return issuer;

                throw new SecurityTokenInvalidIssuerException("Invalid Issuer")
                {
                    InvalidIssuer = issuer
                };
            };
        });

This does feel like I'm sweeping the underlying issue of a buggy and/or misconfigured middleware under the rug and I cannot help thinking that there must be something simpler - this is wizard generated starter code from visual studio 2019 after all.

Twisted
  • 2,939
  • 4
  • 32
  • 54
0

I've found a better way of doing it so thought I would post the answer as an alternative to my first answer.

I found this very good sample application and tutorial and worked through the 1st part "Desktop App Calls Web Api" and produces an api that is using the correct endpoints and handles validates the token in a more predictable manor. If you are stating out on a new Web API I would suggest using this as a starting point instead of the api that visual studio scaffolds.

To get the scaffolded weather forcast api to accept token an access token

  • You need seperate application registrations for the client and the api as well as a scope as sugested by @Carl Zhao and the tutorial linked above.
  • Replace the Microsoft.AspNetCore.Authentication.AzureAD.UI nuget package with Microsoft.Identity.Web
  • In your startup.cs in Configure services replace the call to services.AddAuthentication(...).AddAzureAdBearer(....) with a call to AddMicrosoftIdentityWebApiAuthentication
  • remove using directives for Microsoft.AspNetCore.Authentication.* and add in using Microsoft.Identity.Web;

heres what my Configure Services Looks Like now

    public void ConfigureServices(IServiceCollection services)
    {
        services.AddMicrosoftIdentityWebApiAuthentication(Configuration);

        services.AddControllers();
    }

The custom issuer validator presented in my other answer is not requried and can be removed.

Twisted
  • 2,939
  • 4
  • 32
  • 54