Partilhar via


Tutorial: Proteger uma API da Web do ASP.NET Core registrada em um locatário externo

Esta série de tutoriais demonstra como proteger uma API da Web registrada no locatário externo. Neste tutorial, você criará uma API Web ASP.NET Core que publica permissões delegadas (escopos) e permissões de aplicativo (funções de aplicativo).

Neste tutorial;

  • Configurar sua API da Web para usar seus detalhes de registro de aplicativo
  • Configure sua API da Web para usar permissões delegadas e de aplicativo registradas no registro do aplicativo
  • Proteja seus pontos de extremidade da API da Web

Pré-requisitos

  • Um registro de API que expõe pelo menos um escopo (permissões delegadas) e uma função de aplicativo (permissão de aplicativo), como ToDoList.Read. Se ainda não o fez, registre uma API no centro de administração do Microsoft Entra seguindo as etapas de registro. Certifique-se de ter o seguinte:

    • ID do aplicativo (cliente) da API Web
    • A ID do diretório (locatário) da API Web está registrada
    • Subdomínio de diretório (locatário) de onde a API Web está registrada. Por exemplo, se o domínio principal for contoso.onmicrosoft.com, o subdomínio Diretório (locatário) será contoso.
    • ToDoList.Read e ToDoList.ReadWrite como as permissões delegadas (escopos) expostas pela API da Web.
    • ToDoList.Read.All e ToDoList.ReadWrite.All como as permissões de aplicativo (funções de aplicativo) expostas pela API Web.
  • SDK do .NET 7.0 ou posterior.

  • Visual Studio Code ou outro editor de código.

Criar uma API Web ASP.NET Core

  1. Abra o terminal e, em seguida, navegue até à pasta onde pretende que o seu projeto viva.

  2. Execute os seguintes comandos:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. Quando uma caixa de diálogo perguntar se você deseja adicionar os ativos necessários ao projeto, selecione Sim.

Instalar pacotes

Instale os seguintes pacotes:

  • Microsoft.EntityFrameworkCore.InMemory que permite que o Entity Framework Core seja usado com um banco de dados na memória. Ele não foi projetado para uso em produção.
  • Microsoft.Identity.Web simplifica a adição de suporte de autenticação e autorização a aplicativos Web e APIs da Web que se integram à plataforma de identidade da Microsoft.
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ê registrou depois de registrar sua API da 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 espaços reservados, conforme mostrado:

  • Substitua Enter_the_Application_Id_Here pelo ID do aplicativo (cliente).
  • Substitua Enter_the_Tenant_Id_Here pelo ID do diretório (locatário).
  • Substitua Enter_the_Tenant_Subdomain_Here pelo subdomínio Diretório (locatário).

Usar domínio de URL personalizado (opcional)

Use um domínio personalizado para marcar totalmente a URL de autenticação. Do ponto de vista do usuário, os usuários permanecem no seu domínio durante o processo de autenticação, em vez de serem redirecionados para ciamlogin.com nome de domínio.

Siga estas etapas para usar um domínio personalizado:

  1. Use as etapas em Habilitar domínios de URL personalizados para aplicativos em locatários externos para habilitar o domínio de URL personalizado para seu locatário externo.

  2. Abra appsettings.json arquivo:

    1. Atualize o Instance valor da propriedade para https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here. Substitua Enter_the_Custom_Domain_Here pelo seu domínio de URL personalizado e Enter_the_Tenant_ID_Here pelo seu ID de inquilino. Se não tiver o ID do inquilino, saiba como ler os detalhes do inquilino.
    2. Adicionar knownAuthorities propriedade com um valor [Enter_the_Custom_Domain_Here].

Depois de fazer as alterações no arquivo appsettings.json, se o domínio de URL personalizado estiver login.contoso.com e o ID do locatário for aaaabbbb-0000-cccc-1111-dddd2222eeee, o arquivo deverá ser semelhante ao seguinte trecho:

{
    "AzureAd": {
        "Instance": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here",
        "KnownAuthorities": ["login.contoso.com"]
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

Adicionar função e escopo do aplicativo

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 um mínimo de uma função de aplicativo para aplicativos, também chamada de permissão de aplicativo, para que os aplicativos cliente obtenham um token de acesso como eles mesmos, ou seja, quando eles não estão entrando em um usuário.

Especificamos essas permissões no arquivo appsettings.json . Neste tutorial, registramos 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 do aplicativo.

{
  "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

Um esquema de autenticação é nomeado quando o serviço de autenticação é configurado durante a autenticação. Neste artigo, usamos o esquema de autenticação do portador JWT. Adicione o seguinte código no arquivo 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);

Crie os seus modelos

Crie uma pasta chamada Modelos na pasta raiz do seu projeto. Navegue até a pasta e crie um arquivo chamado ToDo.cs adicione o código a seguir. Esse 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 banco de dados

O contexto do banco de dados é a classe principal que coordena a funcionalidade do Entity Framework para um modelo de dados. Essa classe é criada derivando da classe Microsoft.EntityFrameworkCore.DbContext . Neste tutorial, usamos um banco de dados na memória para fins de teste.

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

  2. Navegue até essa pasta e crie um arquivo chamado ToDoContext.cs adicione o seguinte conteúdo a esse arquivo:

    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 aplicativo e adicione o seguinte código no arquivo. Esse código registra uma DbContext subclasse chamada ToDoContext 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). O contexto é configurado para usar o banco de dados na 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 de uma ação. Normalmente , as ações Criar, Ler, Atualizar e Excluir (CRUD). Neste tutorial, criamos apenas dois itens de ação. Um item de ação de leitura de todos e um item de ação de criação para demonstrar como proteger seus pontos de extremidade.

  1. Navegue até a pasta Controllers na pasta raiz do seu projeto.

  2. Crie um arquivo chamado ToDoListController.cs dentro desta pasta. Abra o arquivo e adicione o seguinte código da placa clichê:

    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 seção, adicionamos código aos espaços reservados que criamos. O foco aqui não é criar a API, mas sim protegê-la.

  1. 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, manipulando a validação de token. Para garantir que nossos pontos de extremidade exijam autorização, usamos o pacote Microsoft.AspNetCore.Authorization incorporado.

  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 é as declarações para 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 reivindicaçã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 pela ID Externa do Microsoft Entra tem pelo menos uma das duas declarações. Os tokens de acesso emitidos para um usuário têm a scp reivindicaçã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 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");
        }
    }
    
  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. Conecte suas definições de permissão para proteger 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 ao ponto de extremidade GET all e ao ponto de extremidade 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);
    }
    

Execute sua API

Execute sua API para garantir que ela esteja funcionando bem sem erros usando o comando dotnet run. Se você pretende usar o protocolo HTTPS mesmo durante o teste, você precisa confiar no . Certificado de desenvolvimento NET.

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

Próximo passo