1
votes

I've been following this Azure sample for securing an AspNet WebApp to a WebApi we own and secure with our own Azure Active Directory organisation

We have an exisitng AspNet site that is already secured with Azure Active Directory so I am really just trying to insert our equivalent of the sample's TodoListService.

The sample uses MSAL so we have moved the site over to use that. The ConfigureServices method in the WebSite Startup is

public void ConfigureServices(IServiceCollection services)
{
    services.AddOptions();

    services.AddMicrosoftIdentityWebAppAuthentication(this.Configuration)
            .EnableTokenAcquisitionToCallDownstreamApi(new string[] { "https://ourdomain/app.our-service/user_impersonation" })
            .AddDownstreamWebApi("OurService", this.Configuration.GetSection("OurServiceApi"))
            .AddInMemoryTokenCaches();

    // Add Apis
    services.AddOurService(this.Configuration);

    services.AddAuthorization(options =>
    {
        options.DefaultPolicy = new AuthorizationPolicyBuilder()
                        .RequireAuthenticatedUser()
                        .RequireClaim(
                                System.Security.Claims.ClaimsIdentity.DefaultRoleClaimType,
                                "Team_Administrators")
                        .Build();
    });

    services.AddRazorPages()
            .AddMicrosoftIdentityUI();

    services.AddServerSideBlazor()
            .AddMicrosoftIdentityConsentHandler();
}

The sample says that the scopes need to be in the format api://<client_id>/scope_name but as you can see our scope name is the AD tenant domain plus the scope. Attempts to use the client id resulted in this error

OpenIdConnectProtocolException: Message contains error: 'invalid_resource', error_description: 'AADSTS500011: The resource principal named api://4f3ca2ab-d7dc-401a-a514-37744ab3555f was not found in the tenant named 1300f116-f07e-427f-b2ef-c66643994577. This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant. You might have sent your authentication request to the wrong tenant.

With domain name format instead we have we are able to authenticate with the website.

We can successfully call the PrepareAuthenticatedClient method as per the sample to get an accessToken

private async Task PrepareAuthenticatedClient()
{
    var accessToken = await this.tokenAcquisition.GetAccessTokenForUserAsync(new[] { this.clinicsSettings.Scopes });
    System.Diagnostics.Debug.WriteLine($"access token-{accessToken}");
    this.httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
    this.httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}

Calling tokenAcquisition.GetAccessTokenForUserAsync results in a redirect request to be made in the browser to AAD and then the user is redirected back. Subsequent calls to get GetAccessTokenForUserAsync all succeed with no redirect.

One we have the AccessToken we attempt to call our web service. That call is rejected from the Web Service as 401 Unauthorised. The specific response is

{StatusCode: 401, ReasonPhrase: 'Unauthorized', Version: 1.1, Content: System.Net.Http.HttpConnectionResponseContent, Headers: {
Transfer-Encoding: chunked Server: Microsoft-IIS/10.0
WWW-Authenticate: Bearer error="invalid_token", error_description="The audience 'https://OURDOMAIN.co.uk/app.our-service' is invalid" X-Powered-By: ASP.NET Date: Wed, 21 Oct 2020 17:16:01 GMT }}

The Startup class looks like this for the Web Service

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

// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    if (env.IsDevelopment())
    {
        // Since IdentityModel version 5.2.1 (or since Microsoft.AspNetCore.Authentication.JwtBearer version 2.2.0),
        // PII hiding in log files is enabled by default for GDPR concerns.
        // For debugging/development purposes, one can enable additional detail in exceptions by setting IdentityModelEventSource.ShowPII to true.
        Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
        app.UseDeveloperExceptionPage();
    }
    else
    {
        app.UseHsts();
    }

    app.UseHttpsRedirection();
    app.UseAuthentication();
    app.UseRouting();
    app.UseAuthorization();

    app.UseEndpoints(endpoints =>
    {
        endpoints.MapControllers();
    });
}

Can anyone help explain why the accessToken is not enough to get a successful call to the webservice?

1

1 Answers

1
votes

For a v2.0 access token, the format of the scope is "api://{ClientIdOfWebApi}/scope" unless your tenant has a verified domain, in which case this will be "yourdomain/scope"

You can know it by looking at the AppId URI in the app registration portal.

Depending on the version of the token, Microsoft.Identity.Web checks the audience of the shape above, but when you have your domain, you need to add:

"Audience": "yourdomain"

in the appsettings.json of your Web API.

See: https://docs.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration#case-where-you-used-a-custom-app-id-uri-for-your-web-api