Por que Http 401 ao chamar AspNet Core API protegida com AAD. Nosso WebApp, Web API e AD Org

Oct 20 2020

Tenho seguido este exemplo do Azure para proteger um AspNet WebApp para um WebApi que possuímos e protegemos com nossa própria organização do Azure Active Directory

Temos um site AspNet existente que já está protegido com o Azure Active Directory, então estou apenas tentando inserir nosso equivalente do TodoListService do exemplo.

O exemplo usa MSAL, então mudamos o site para usá-lo. O método ConfigureServices no WebSite Startup é

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();
}

O exemplo diz que os escopos precisam estar no formato api: // <client_id> / scope_name, mas como você pode ver, nosso nome de escopo é o domínio de locatário do AD mais o escopo. As tentativas de usar o ID do cliente resultaram neste erro

OpenIdConnectProtocolException: A mensagem contém o erro: 'invalid_resource', error_description: 'AADSTS500011: O principal do recurso denominado api: // 4f3ca2ab-d7dc-401a-a514-37744ab3555f não foi encontrado no locatário denominado 1300f116-f07e-c647f. Isso pode acontecer se o aplicativo não tiver sido instalado pelo administrador do locatário ou consentido por qualquer usuário no locatário. Você pode ter enviado sua solicitação de autenticação para o locatário errado.

Com o formato de nome de domínio, em vez disso, somos capazes de autenticar no site.

Podemos chamar com sucesso o PrepareAuthenticatedClientmétodo de acordo com a amostra para obter um 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"));
}

A chamada tokenAcquisition.GetAccessTokenForUserAsyncresulta em uma solicitação de redirecionamento a ser feita no navegador para o AAD e, em seguida, o usuário é redirecionado de volta. Chamadas subsequentes para obter GetAccessTokenForUserAsyncsucesso sem redirecionamento.

Um que temos o AccessToken que tentamos chamar nosso serviço web. Essa chamada foi rejeitada do serviço da Web como 401 não autorizado. A resposta específica é

{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 = "O público 'https://OURDOMAIN.co.uk/app.our-service' é inválido" X-Powered-By: ASP.NET Date: Wed, 21 Out 2020 17:16:01 GMT}}

A classe de inicialização tem esta aparência para o serviço da web

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();
    });
}

Alguém pode ajudar a explicar por que o accessToken não é suficiente para obter uma chamada bem-sucedida para o serviço da web?

Respostas

1 Jean-MarcPrieur Oct 22 2020 at 13:47

Para um token de acesso v2.0, o formato do escopo é "api: // {ClientIdOfWebApi} / scope", a menos que seu locatário tenha um domínio verificado, caso em que será "yourdomain / scope"

Você pode saber disso olhando o URI do AppId no portal de registro do aplicativo.

Dependendo da versão do token, Microsoft.Identity.Web verifica o público da forma acima, mas quando você tem seu domínio, é necessário adicionar:

"Público": "seudomínio"

no appsettings.json de sua API da Web.

Vejo: 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