Tutorial: Proteger uma API Web ASP.NET registada num inquilino do cliente
As APIs Web podem conter informações que requerem autenticação e autorização do utilizador. As aplicações podem utilizar o acesso delegado, agindo em nome de um utilizador com sessão iniciada ou acesso apenas à aplicação, agindo apenas como a própria identidade da aplicação ao chamar APIs Web protegidas.
Neste tutorial, criamos uma API Web que publica permissões delegadas (âmbitos) e permissões de aplicação (funções de aplicação). As aplicações cliente, como aplicações Web que adquirem tokens em nome de um utilizador com sessão iniciada, utilizam as permissões delegadas. As aplicações cliente, como aplicações daemon que adquirem tokens para si mesmas, utilizam as permissões da aplicação.
Neste tutorial, ficará a saber como:
Configure a sua API Web tp use it's app registration details Configure your Web API to use delegated and application permissions registered in the app registration Protect your Web API endpoints (Configurar a API Web para utilizar permissões delegadas e de aplicações registadas no registo de aplicações Proteger os pontos finais da API Web)
Pré-requisitos
Um registo de API que expõe pelo menos um âmbito (permissões delegadas) e uma função de aplicação (permissão de aplicação), como ToDoList.Read. Se ainda não o fez, registe uma API no centro de administração do Microsoft Entra ao seguir os passos de registo. Certifique-se de que tem o seguinte:
- ID da aplicação (cliente) da API Web
- O ID do diretório (inquilino) da API Web está registado
- Subdomínio de diretório (inquilino) do local onde a API Web está registada. Por exemplo, se o seu domínio principal for contoso.onmicrosoft.com, o subdomínio do diretório (inquilino) é contoso.
- ToDoList.Read e ToDoList.ReadWrite como as permissões delegadas (âmbitos) expostas pela API Web.
- ToDoList.Read.All e ToDoList.ReadWrite.All como as permissões da aplicação (funções de aplicação) expostas pela API Web.
.NET 7.0 ou posterior.
Visual Studio Code ou outro editor de código.
Criar uma API Web ASP.NET Core
Abra o terminal e, em seguida, navegue para a pasta onde pretende que o seu projeto esteja ativo.
Execute os seguintes comandos:
dotnet new webapi -o ToDoListAPI cd ToDoListAPI
Quando uma caixa de diálogo perguntar se pretende adicionar recursos necessários ao projeto, selecione Sim.
Instalar pacotes
Instale os seguintes pacotes:
Microsoft.EntityFrameworkCore.InMemory
que permite que o Entity Framework Core seja utilizado com uma base de dados dentro da memória. Não foi concebido para utilização de produção.Microsoft.Identity.Web
simplifica a adição de suporte de autenticação e autorização a aplicações Web e APIs Web integradas no plataforma de identidades da Microsoft.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web
Configurar detalhes do registo de aplicações
Abra o ficheiro appsettings.json na pasta da aplicação e adicione os detalhes de registo da aplicação que gravou após registar a API Web.
{
"AzureAd": {
"Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
"TenantId": "Enter_the_Tenant_Id_Here",
"ClientId": "Enter_the_Application_Id_Here",
},
"Logging": {...},
"AllowedHosts": "*"
}
Substitua os seguintes marcadores de posição, conforme mostrado:
- Substitua pelo
Enter_the_Application_Id_Here
ID da aplicação (cliente). - Substitua
Enter_the_Tenant_Id_Here
pelo ID do Diretório (inquilino). - Substitua
Enter_the_Tenant_Subdomain_Here
pelo subdomínio diretório (inquilino).
Adicionar função e âmbito da aplicação
Todas as APIs têm de publicar um mínimo de um âmbito, também denominado permissão delegada, para que as aplicações cliente obtenham um token de acesso para um utilizador com êxito. As APIs também devem publicar um mínimo de uma função de aplicação para aplicações, também denominada permissão de aplicação, para que as aplicações cliente obtenham um token de acesso como elas próprias, ou seja, quando não estão a iniciar sessão num utilizador.
Especificamos estas permissões no ficheiro appsettings.json . Neste tutorial, registámos quatro permissões. ToDoList.ReadWrite e ToDoList.Read como as permissões delegadas e ToDoList.ReadWrite.All e ToDoList.Read.All como as permissões da aplicação.
{
"AzureAd": {
"Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
"TenantId": "Enter_the_Tenant_Id_Here",
"ClientId": "Enter_the_Application_Id_Here",
"Scopes": {
"Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
"Write": ["ToDoList.ReadWrite"]
},
"AppPermissions": {
"Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
"Write": ["ToDoList.ReadWrite.All"]
}
},
"Logging": {...},
"AllowedHosts": "*"
}
Adicionar esquema de autenticação
É atribuído um nome a um esquema de autenticação quando o serviço de autenticação é configurado durante a autenticação. Neste artigo, utilizamos o esquema de autenticação do portador JWT. Adicione o seguinte código no ficheiro Programs.cs para adicionar um esquema de autenticação.
// Add the following to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
// Add authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration);
Criar os seus modelos
Crie uma pasta denominada Modelos na pasta raiz do seu projeto. Navegue para a pasta e crie um ficheiro chamado ToDo.cs e, em seguida, adicione o seguinte código. Este código cria um modelo chamado ToDo.
using System;
namespace ToDoListAPI.Models;
public class ToDo
{
public int Id { get; set; }
public Guid Owner { get; set; }
public string Description { get; set; } = string.Empty;
}
Adicionar um contexto de base de dados
O contexto da base de dados é a classe principal que coordena a funcionalidade Entity Framework para um modelo de dados. Esta classe é criada ao derivar da classe Microsoft.EntityFrameworkCore.DbContext . Neste tutorial, utilizamos uma base de dados dentro da memória para fins de teste.
Crie uma pasta denominada DbContext na pasta raiz do projeto.
Navegue para essa pasta e crie um ficheiro chamado ToDoContext.cs e, em seguida, adicione os seguintes conteúdos a esse ficheiro:
using Microsoft.EntityFrameworkCore; using ToDoListAPI.Models; namespace ToDoListAPI.Context; public class ToDoContext : DbContext { public ToDoContext(DbContextOptions<ToDoContext> options) : base(options) { } public DbSet<ToDo> ToDos { get; set; } }
Abra o ficheiro Program.cs na pasta raiz da sua aplicação e, em seguida, adicione o seguinte código no ficheiro. Este código regista uma
DbContext
subclasse denominadaToDoContext
serviço de âmbito no fornecedor de serviços de aplicações ASP.NET Core (também conhecido como contentor de injeção de dependências). O contexto está configurado para utilizar a base de dados dentro da memória.// Add the following to your imports using ToDoListAPI.Context; using Microsoft.EntityFrameworkCore; builder.Services.AddDbContext<ToDoContext>(opt => opt.UseInMemoryDatabase("ToDos"));
Adicionar controladores
Na maioria dos casos, um controlador teria mais do que uma ação. Normalmente, as ações Criar, Ler, Atualizar e Eliminar (CRUD). Neste tutorial, criamos apenas dois itens de ação. Leia todos os itens de ação e um item de ação de criação para demonstrar como proteger os seus pontos finais.
Navegue para a pasta Controladores na pasta raiz do projeto.
Crie um ficheiro chamado ToDoListController.cs dentro desta pasta. Abra o ficheiro e, em seguida, adicione o seguinte código de placa de caldeira:
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; using Microsoft.Identity.Web; using Microsoft.Identity.Web.Resource; using ToDoListAPI.Models; using ToDoListAPI.Context; namespace ToDoListAPI.Controllers; [Authorize] [Route("api/[controller]")] [ApiController] public class ToDoListController : ControllerBase { private readonly ToDoContext _toDoContext; public ToDoListController(ToDoContext toDoContext) { _toDoContext = toDoContext; } [HttpGet()] [RequiredScopeOrAppPermission()] public async Task<IActionResult> GetAsync(){...} [HttpPost] [RequiredScopeOrAppPermission()] public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...} private bool RequestCanAccessToDo(Guid userId){...} private Guid GetUserId(){...} private bool IsAppMakingRequest(){...} }
Adicionar código ao controlador
Nesta secção, adicionamos código aos marcadores de posição que criámos. O foco aqui não é criar a API, mas sim protegê-la.
Importe os pacotes necessários. O pacote Microsoft.Identity.Web é um wrapper MSAL que nos ajuda a lidar facilmente com a lógica de autenticação, por exemplo, ao processar a validação de tokens. Para garantir que os nossos pontos finais necessitam de autorização, utilizamos o pacote microsoft.AspNetCore.Authorization incorporado.
Uma vez que concedemos permissões para que esta API seja chamada através de permissões delegadas em nome do utilizador ou das permissões de aplicação em que o cliente chama como ela própria e não em nome do utilizador, é importante saber se a chamada está a ser efetuada pela aplicação em seu próprio nome. A forma mais fácil de o fazer são as afirmações para determinar se o token de acesso contém a
idtyp
afirmação opcional. Estaidtyp
afirmação é a forma mais fácil para a API determinar se um token é um token de aplicação ou um token de aplicação + utilizador. Recomendamos que ative aidtyp
afirmação opcional.Se a
idtyp
afirmação não estiver ativada, pode utilizar asroles
afirmações escp
para determinar se o token de acesso é um token de aplicação ou um token de aplicação + utilizador. Um token de acesso emitido pelo ID externo Microsoft Entra tem, pelo menos, uma das duas afirmações. Os tokens de acesso emitidos a um utilizador têm ascp
afirmação. Os tokens de acesso emitidos para uma aplicação têm aroles
afirmação. Os tokens de acesso que contêm ambas as afirmações são emitidos apenas para os utilizadores, em que ascp
afirmação designa as permissões delegadas, enquanto aroles
afirmação designa a função do utilizador. Os tokens de acesso que não têm nenhum deles não devem ser honrados.private bool IsAppMakingRequest() { if (HttpContext.User.Claims.Any(c => c.Type == "idtyp")) { return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app"); } else { return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp"); } }
Adicione uma função auxiliar que determina se o pedido que está a ser feito contém permissões suficientes para realizar a ação pretendida. Verifique se é a aplicação que está a fazer o pedido em seu próprio nome ou se a aplicação está a efetuar a chamada em nome de um utilizador que possua o recurso especificado ao validar o ID de utilizador.
private bool RequestCanAccessToDo(Guid userId) { return IsAppMakingRequest() || (userId == GetUserId()); } private Guid GetUserId() { Guid userId; if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId)) { throw new Exception("User ID is not valid."); } return userId; }
Ligue as definições de permissão para proteger rotas. Proteja a API ao adicionar o
[Authorize]
atributo à classe de controlador. Isto garante que as ações do controlador só podem ser chamadas se a API for chamada com uma identidade autorizada. As definições de permissão definem que tipos de permissões são necessárias para efetuar estas ações.[Authorize] [Route("api/[controller]")] [ApiController] public class ToDoListController: ControllerBase{...}
Adicione permissões ao ponto final GET all e ao ponto final POST. Faça-o com o método RequiredScopeOrAppPermission que faz parte do espaço de nomes Microsoft.Identity.Web.Resource . Em seguida, passa âmbitos e permissões para este método através dos atributos RequiredScopesConfigurationKey e RequiredAppPermissionsConfigurationKey .
[HttpGet] [RequiredScopeOrAppPermission( RequiredScopesConfigurationKey = "AzureAD:Scopes:Read", RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read" )] public async Task<IActionResult> GetAsync() { var toDos = await _toDoContext.ToDos! .Where(td => RequestCanAccessToDo(td.Owner)) .ToListAsync(); return Ok(toDos); } [HttpPost] [RequiredScopeOrAppPermission( RequiredScopesConfigurationKey = "AzureAD:Scopes:Write", RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write" )] public async Task<IActionResult> PostAsync([FromBody] ToDo toDo) { // Only let applications with global to-do access set the user ID or to-do's var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId(); var newToDo = new ToDo() { Owner = ownerIdOfTodo, Description = toDo.Description }; await _toDoContext.ToDos!.AddAsync(newToDo); await _toDoContext.SaveChangesAsync(); return Created($"/todo/{newToDo!.Id}", newToDo); }
Executar a API
Execute a API para garantir que está a funcionar corretamente sem erros ao utilizar o comando dotnet run
. Se pretender utilizar o protocolo https mesmo durante os testes, tem de confiar em . Certificado de desenvolvimento do NET.
Para obter um exemplo completo deste código de API, veja o ficheiro de exemplos.