Por que Http 401 ao chamar AspNet Core API protegida com AAD. Nosso WebApp, Web API e AD Org
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
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