Partilhar via


Tutorial: Criar e proteger uma API Web ASP.NET Core com a plataforma de identidade da Microsoft

Aplica-se a: círculo verde com um símbolo de marca de seleção branco que indica que o conteúdo a seguir se aplica aos locatários da força de trabalho. Locatários da força de trabalho Círculo verde com um símbolo de marca de seleção branco que indica que o conteúdo a seguir se aplica a locatários externos. Inquilinos externos (saiba mais)

Esta série de tutoriais demonstra como proteger uma API Web ASP.NET Core com a plataforma de identidade da Microsoft para limitar seu acesso apenas a usuários autorizados e aplicativos cliente. A API da Web que você cria usa permissões delegadas (escopos) e permissões de aplicativo (funções de aplicativo).

Neste tutorial, você:

  • Crie uma API Web ASP.NET Core
  • Configurar a API web para usar os detalhes de registo da aplicação Microsoft Entra
  • Proteja os seus endpoints da API da web
  • Execute a API da Web para garantir que ela esteja ouvindo solicitações HTTP

Pré-requisitos

Criar um novo projeto de API Web ASP.NET Core

Para criar um projeto mínimo de API Web ASP.NET Core, siga estas etapas:

  1. Abra seu terminal no Visual Studio Code ou em qualquer outro editor de código e navegue até o diretório onde você deseja criar seu projeto.

  2. Execute os seguintes comandos na CLI do .NET ou em qualquer outra ferramenta de linha de comando.

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. Selecione Sim quando uma caixa de diálogo perguntar se você deseja confiar nos autores.

  4. Selecione Sim Quando uma caixa de diálogo perguntar se você deseja adicionar os ativos necessários ao projeto.

Instalar pacotes necessários

Para criar, proteger e testar a API Web ASP.NET Core, você precisa instalar os seguintes pacotes:

  • Microsoft.EntityFrameworkCore.InMemory- Um pacote que permite usar o Entity Framework Core com um banco de dados na memória. É útil para fins de teste, mas não foi projetado para uso em produção.
  • Microsoft.Identity.Web - um conjunto de bibliotecas ASP.NET Core que simplificam a adição de suporte de autenticação e autorização a aplicações Web e APIs Web que se integram com a plataforma de identidade da Microsoft.

Para instalar o pacote, use:

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Configurar detalhes de registro do aplicativo

Abra o arquivo appsettings.json na pasta do aplicativo e adicione os detalhes de registro do aplicativo que você gravou depois de registrar a API da Web.

{
    "AzureAd": {
        "Instance": "Enter_the_Authority_URL_Here",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here"
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

Substitua os seguintes marcadores de posição, conforme mostrado:

  • Substitua Enter_the_Application_Id_Here pelo ID do aplicativo (cliente).
  • Substitua Enter_the_Tenant_Id_Here pelo ID do diretório (tenant).
  • Substitua Enter_the_Authority_URL_Here pelo URL da Authority, conforme explicado na próxima seção.

URL de autoridade para seu aplicativo

A URL de autoridade especifica o diretório do qual a Biblioteca de Autenticação da Microsoft (MSAL) pode solicitar tokens. Você o constrói de forma diferente tanto na força de trabalho quanto nos locatários externos, conforme mostrado:

//Instance for workforce tenant
Instance: "https://login.microsoftonline.com/"

Usar domínio de URL personalizado (opcional)

Domínios de URL personalizados não são suportados em locatários da força de trabalho.

Adicionar permissões

Todas as APIs devem publicar um mínimo de um escopo, também chamado de permissão delegada, para que os aplicativos cliente obtenham um token de acesso para um usuário com êxito. As APIs também devem publicar pelo menos uma função de aplicativo, também chamadas de permissões de aplicativo, para que as aplicações cliente possam obter um token de acesso por si mesmas, ou seja, quando um utilizador não está a iniciar sessão.

Especificamos essas permissões no arquivo appsettings.json. Neste tutorial, você registrou as seguintes permissões delegadas e de aplicativo:

  • Permissões delegadas:ToDoList.Read e ToDoList.ReadWrite.
  • Permissões de aplicativos:ToDoList.Read.All e ToDoList.ReadWrite.All.

Quando um usuário ou aplicativo cliente chama a API da Web, somente os clientes com esses escopos ou permissões são autorizados a acessar o ponto de extremidade protegido.

{
  "AzureAd": {
    "Instance": "Enter_the_Authority_URL_Here",
    "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": "*"
}

Implementar autenticação e autorização na API

Para configurar a autenticação e autorização, abra o arquivo program.cs e substitua seu conteúdo os seguintes trechos de código:

Adicionar um esquema de autenticação

Nesta API, usamos o esquema JSON Web Token (JWT) Bearer como o mecanismo de autenticação padrão. Use o método AddAuthentication para registrar o esquema de portador JWT.

// Add required packages to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add an authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

Crie o modelo do seu aplicativo

Na pasta raiz do projeto, crie uma pasta chamada Modelos. Navegue até a pasta Modelos e crie um arquivo chamado ToDo.cs e adicione o código a seguir.

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

O código anterior cria um modelo chamado ToDo. Esse modelo representa os dados gerenciados pelo aplicativo.

Adicionar um contexto de banco de dados

Em seguida, definimos uma classe de contexto de banco de dados, que coordena a funcionalidade do Entity Framework para um modelo de dados. Essa classe herda da classe Microsoft.EntityFrameworkCore.DbContext que gerencia interações entre o aplicativo e o banco de dados. Para adicionar o contexto do banco de dados, execute estas etapas:

  1. Crie uma pasta chamada DbContext na pasta raiz do seu projeto.

  2. Navegue até a pasta DbContext e crie um arquivo chamado ToDoContext.cs e adicione o seguinte código:

    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; }
    }
    
  3. Abra o arquivo Program.cs na pasta raiz do seu projeto e atualize-o com o seguinte código:

    // Add the following to your imports
    using ToDoListAPI.Context;
    using Microsoft.EntityFrameworkCore;
    
    //Register ToDoContext as a service in the application
    builder.Services.AddDbContext<ToDoContext>(opt =>
        opt.UseInMemoryDatabase("ToDos"));
    

No trecho de código anterior, registramos o Contexto de Banco de Dados como um serviço com escopo no provedor de serviços de aplicativo ASP.NET Core (também conhecido como contêiner de injeção de dependência). Você também configura a ToDoContext classe para usar um banco de dados na memória para a API de lista de tarefas.

Configurar um controlador

Os controladores normalmente implementam ações de Criar, Ler, Atualizar e Excluir (CRUD) para gerenciar recursos. Como este tutorial se concentra mais na proteção dos pontos de extremidade da API, implementamos apenas dois itens de ação no controlador. Uma ação de leitura total para recuperar todos os itens To-Do e uma ação de criação para adicionar um novo item To-Do. Siga estas etapas para adicionar um controlador ao seu projeto:

  1. Navegue até a pasta raiz do seu projeto e crie uma pasta chamada Controllers.

  2. Crie um ficheiro chamado ToDoListController.cs dentro da pasta Controllers e adicione o seguinte código padrão:

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

Esta seção explica como adicionar código ao controlador estruturado na seção anterior. O foco aqui é proteger a API, não criá-la.

  1. Importe os pacotes necessários: O Microsoft.Identity.Web pacote é um wrapper em torno MSAL.NET que nos ajuda a lidar facilmente com a lógica de autenticação, como lidar com a validação de token. Para garantir que nossos endpoints exijam autorização, usamos o pacote embutido Microsoft.AspNetCore.Authorization .

  2. Como concedemos permissões para que essa API seja chamada usando permissões delegadas em nome do usuário ou permissões de aplicativo em que o cliente chama como ele mesmo e não em nome do usuário, é importante saber se a chamada está sendo feita pelo aplicativo em seu próprio nome. A maneira mais fácil de fazer isso é descobrir se o token de acesso contém a idtyp declaração opcional. Essa idtyp declaração é a maneira mais fácil para a API determinar se um token é um token de aplicativo ou um token de aplicativo + de usuário. Recomendamos ativar a idtyp declaração opcional.

    Se a idtyp declaração não estiver habilitada, você poderá usar as roles declarações e scp para determinar se o token de acesso é um token de aplicativo ou um token de aplicativo + de usuário. Um token de acesso emitido pelo Microsoft Entra ID tem pelo menos uma das duas declarações. Os tokens de acesso emitidos para um utilizador têm a scp declaração. Os tokens de acesso emitidos para um aplicativo têm a roles reivindicação. Os tokens de acesso que contêm ambas as declarações são emitidos apenas para usuários, onde a scp declaração designa as permissões delegadas, enquanto a roles declaração designa a função do usuário. Tokens de acesso que não têm nenhum dos dois atributos não devem ser aceites.

    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");
        }
    }
    
  3. Adicione uma função auxiliar que determine se a solicitação que está sendo feita contém permissões suficientes para executar a ação pretendida. Verifique se é o aplicativo que está fazendo a solicitação em seu próprio nome ou se o aplicativo está fazendo a chamada em nome de um usuário que possui o recurso determinado, validando o ID do usuário.

    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;
        }
    
  4. Insira as suas definições de permissão para proteger as rotas. Proteja sua API adicionando o [Authorize] atributo à classe do controlador. Isso garante que as ações do controlador possam ser chamadas somente se a API for chamada com uma identidade autorizada. As definições de permissão definem quais tipos de permissões são necessários para executar essas ações.

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController: ControllerBase{...}
    

    Adicione permissões aos endpoints GET e POST. Faça isso usando o método RequiredScopeOrAppPermission que faz parte do namespace Microsoft.Identity.Web.Resource . Em seguida, você passa escopos e permissões para esse método por meio 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);
    }
    

Configurar o middleware da API para usar o controlador

Em seguida, configuramos o aplicativo para reconhecer e usar controladores para lidar com solicitações HTTP. Abra o program.cs arquivo e adicione o código a seguir para registrar os serviços do controlador no contêiner de injeção de dependência.


builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();

app.Run();

No trecho de código anterior, o AddControllers() método prepara o aplicativo para usar controladores registrando os serviços necessários enquanto MapControllers() mapeia as rotas do controlador para lidar com solicitações HTTP de entrada.

Executa a tua API

Execute sua API para garantir que ela esteja sendo executada sem erros usando o comando dotnet run. Se pretender usar o protocolo HTTPS mesmo durante o teste, precisa confiar no certificado de desenvolvimento do .NET.

  1. Inicie o aplicativo digitando o seguinte no terminal:

    dotnet run
    
  2. Uma saída semelhante à seguinte deve ser exibida no terminal, confirmando que a aplicação está a ser executada em http://localhost:{port} e a aguardar solicitações.

    Building...
    info: Microsoft.Hosting.Lifetime[0]
        Now listening on: http://localhost:{port}
    info: Microsoft.Hosting.Lifetime[0]
        Application started. Press Ctrl+C to shut down.
    ...
    

A página da Web http://localhost:{host} exibe uma saída semelhante à imagem a seguir. Isso ocorre porque a API está sendo chamada sem autenticação. Para fazer uma chamada autorizada, consulte Próximas etapas para obter orientação sobre como acessar uma API da Web protegida.

Captura de tela que mostra o erro 401 quando a página da Web é iniciada.

Para obter um exemplo completo do código desta API, consulte o arquivo de exemplos .

Próximos passos