Implementar autorização em APIs Web com Microsoft. Identity.Web

Neste artigo, você implementa a autorização em ASP.NET Core APIs Web usando Microsoft. Identity.Web. Você validará escopos (permissões delegadas) e permissões de aplicativo para controlar o acesso aos recursos protegidos. Os exemplos usam Microsoft Entra ID como o provedor de identidade.

Entender os conceitos de autorização

Esta seção aborda as principais diferenças entre autenticação e autorização e descreve o que Microsoft. Identity.Web valida em tokens de acesso.

Autenticação versus autorização

Conceito Propósito Resultado
Autenticação Verificar identidade 401 Não autorizado em caso de falha
Autorização Verificar permissões 403 Proibido se insuficiente

O que é validado

Quando uma API Web recebe um token de acesso, Microsoft. Identity.Web valida:

  1. Assinatura de token - É de uma autoridade confiável?
  2. Audience do token – Ele é destinado a esta API?
  3. Expiração do token – Ainda é válido?
  4. Escopos/Funções - O aplicativo cliente e o assunto (usuário) têm as permissões certas?

Este guia se concentra em #4 – validando escopos e permissões de aplicativo.

Escopos (permissões delegadas)

Os escopos se aplicam quando um usuário delega permissão a um aplicativo para agir em seu nome (por exemplo, uma API Web chamada em nome de um usuário conectado).

Detalhes Valor
Declaração de token scp ou scope (aplicativo cliente); roles (usuário)
Valores de exemplo "access_as_user" "User.Read", "Files.ReadWrite"

Permissões de aplicativo (permissões de aplicativo)

As permissões de aplicativo se aplicam quando um aplicativo chama a API Web como ela mesma sem contexto de usuário, como um daemon ou um serviço em segundo plano usando credenciais de cliente.

Detalhes Valor
Declaração de token roles
Valores de exemplo "Mail.Read.All", "User.Read.All"

Validar escopos com RequiredScope

O RequiredScope atributo verifica se o token de acesso contém pelo menos um dos escopos especificados. Use esse atributo quando a API atender apenas às solicitações delegadas pelo usuário.

Configurar a validação de escopo

Siga estas etapas para habilitar a validação de escopo em sua API.

1. Habilitar a autorização em sua API:

Adicione serviços de autenticação e autorização ao pipeline de aplicativos:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(); // Required for authorization

var app = builder.Build();

app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();

app.Run();

2. Proteger controladores ou ações:

Aplique os atributos [Authorize] e [RequiredScope] ao seu controlador ou a ações individuais:

using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Only accessible if token has "access_as_user" scope
        return Ok(new[] { "Todo 1", "Todo 2" });
    }
}

Aplicar padrões de escopo

Escolha o padrão que melhor se ajusta à maneira como você gerencia escopos em seu aplicativo.

Padrão 1: escopos definidos em código

Use esse padrão quando os escopos forem fixos e conhecidos no momento do desenvolvimento.

[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
    // All actions require "access_as_user" scope
}

Para aceitar qualquer um dos vários escopos, liste-os como parâmetros:

[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
    // Token must have "read" OR "write" OR "admin"
}

Padrão 2: Escopos a partir da configuração

Use esse padrão quando os escopos devem ser configuráveis por ambiente. Defina os escopos no arquivo de configuração:

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user read write"
  }
}

Faça referência à chave de configuração em seu controlador:

[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
    // Scopes read from configuration
}

Essa abordagem permite alterar escopos sem recompilar.

Padrão 3: escopos no nível da ação

Use esse padrão quando ações diferentes exigirem permissões diferentes. Aplique [RequiredScope] aos métodos de ação individuais:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [RequiredScope("read")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [RequiredScope("write")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        // Only tokens with "write" scope can create
        return CreatedAtAction(nameof(GetTodos), todo);
    }

    [HttpDelete("{id}")]
    [RequiredScope("admin")]
    public IActionResult DeleteTodo(int id)
    {
        // Only tokens with "admin" scope can delete
        return NoContent();
    }
}

Entender o fluxo de validação

Quando uma solicitação chega, o middleware a processa na seguinte ordem:

  1. O middleware de autenticação do ASP.NET Core valida o token.
  2. RequiredScope verificação de atributos para a reivindicação scp ou scope
  3. Se o token contiver pelo menos um escopo correspondente, a solicitação continuará.
  4. Se nenhum escopo correspondente for encontrado, a API retornará uma resposta 403 Proibida.

O exemplo a seguir mostra uma resposta de erro típica:

{
  "error": "insufficient_scope",
  "error_description": "The token does not have the required scope 'access_as_user'."
}

Validar permissões de aplicativo com RequiredScopeOrAppPermission

O RequiredScopeOrAppPermission atributo valida escopos (delegados) ou permissões de aplicativo (aplicativo). Use esse atributo quando a API atender a aplicativos com delegação de usuário e aplicativos daemon/serviço do mesmo ponto de extremidade.

Se a API atender apenas às solicitações delegadas pelo usuário, use RequiredScope em vez disso.

Configurar a validação das permissões de escopo ou de aplicativo

Aplique o atributo para aceitar qualquer tipo de token:

using Microsoft.Identity.Web.Resource;

[Authorize]
[RequiredScopeOrAppPermission(
    AcceptedScope = new[] { "access_as_user" },
    AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
    [HttpGet]
    public IActionResult GetTodos()
    {
        // Accessible with EITHER:
        // - User-delegated token with "access_as_user" scope, OR
        // - App-only token with "TodoList.ReadWrite.All" app permission
        return Ok(todos);
    }
}

Configurar permissões de aplicativo a partir de configurações

Armazene escopos e permissões de aplicativo na configuração para alterá-los sem recompilar.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "TenantId": "your-tenant-id",
    "ClientId": "your-api-client-id",
    "Scopes": "access_as_user",
    "AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
  }
}

Faça referência às chaves de configuração no controlador:

[Authorize]
[RequiredScopeOrAppPermission(
    RequiredScopesConfigurationKey = "AzureAd:Scopes",
    RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
    // Scopes and app permissions from configuration
}

Comparar diferenças de reivindicações de token

A tabela a seguir mostra como as declarações diferem entre tokens delegados pelo usuário e somente de aplicativo:

Tipo de token Reclamação Exemplo de valor
Atribuído pelo usuário scp ou scope "access_as_user User.Read"
Somente aplicativo roles ["TodoList.ReadWrite.All"]

O exemplo a seguir mostra um token delegado pelo usuário:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "scp": "access_as_user",
  "sub": "user-object-id",
  ...
}

O exemplo a seguir mostra um token somente de aplicativo:

{
  "aud": "api://your-api-client-id",
  "iss": "https://login.microsoftonline.com/.../v2.0",
  "roles": ["TodoList.ReadWrite.All"],
  "sub": "app-object-id",
  ...
}

Criar políticas de autorização

Para cenários complexos de autorização, use ASP.NET Core políticas de autorização. As políticas permitem centralizar regras, combinar vários requisitos e gravar a lógica de autorização testável.

Benefit Descrição
Lógica centralizada Definir regras de autorização uma vez, reutilizar em todos os lugares
Componível Combinar vários requisitos (escopos + afirmações + lógica personalizada)
Testável Lógica de autorização mais fácil de testar de forma unitária.
Flexível Requisitos personalizados que excedem o escopo de validação

Padrão 1: Definir uma política com RequireScope

Defina políticas nomeadas que exijam escopos específicos e, em seguida, faça referência a elas em seus controladores:

using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("TodoReadPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("read", "access_as_user");
    });

    options.AddPolicy("TodoWritePolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("write", "admin");
    });
});

var app = builder.Build();

Aplique as políticas às ações do controlador:

[Authorize]
public class TodoListController : ControllerBase
{
    [HttpGet]
    [Authorize(Policy = "TodoReadPolicy")]
    public IActionResult GetTodos()
    {
        return Ok(todos);
    }

    [HttpPost]
    [Authorize(Policy = "TodoWritePolicy")]
    public IActionResult CreateTodo([FromBody] Todo todo)
    {
        return CreatedAtAction(nameof(GetTodos), todo);
    }
}

Padrão 2: Definir uma política com ScopeAuthorizationRequirement

Use ScopeAuthorizationRequirement para requisitos de escopo mais explícitos:

using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("CustomPolicy", policyBuilder =>
    {
        policyBuilder.AddRequirements(
            new ScopeAuthorizationRequirement(new[] { "access_as_user" })
        );
    });
});

Padrão 3: Definir uma política padrão

Defina uma política padrão que se aplique a todos os [Authorize] atributos automaticamente:

builder.Services.AddAuthorization(options =>
{
    var defaultPolicy = new AuthorizationPolicyBuilder()
        .RequireScope("access_as_user")
        .Build();

    options.DefaultPolicy = defaultPolicy;
});

Cada [Authorize] atributo agora requer o access_as_user escopo:

[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
    // All actions protected by default policy
}

Padrão 4: Combinar vários requisitos

Combine os requisitos de escopo, função e autenticação em uma única política:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminPolicy", policyBuilder =>
    {
        policyBuilder.RequireScope("admin");
        policyBuilder.RequireRole("Admin"); // Also check role claim
        policyBuilder.RequireAuthenticatedUser();
    });
});

Padrão 5: criar uma política com base na configuração

Carregue escopos da configuração para manter as políticas específicas do ambiente:

var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("ApiAccessPolicy", policyBuilder =>
    {
        if (requiredScopes != null)
        {
            policyBuilder.RequireScope(requiredScopes);
        }
    });
});

Filtrar solicitações por locatário

Restrinja o acesso à API a tokens de locatários Microsoft Entra específicos. Isso é útil quando sua API multilocatário só deve aceitar solicitações de locatários aprovados do cliente.

Restringir o acesso a locatários permitidos

Defina uma política que verifica a declaração de ID do locatário em relação a uma lista de permissões:

builder.Services.AddAuthorization(options =>
{
    string[] allowedTenants =
    {
        "14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
        "af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
        "979f4440-75dc-4664-b2e1-2cafa0ac67d1"  // Northwind tenant
    };

    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });

    // Apply to all endpoints by default
    options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});

Configurar a filtragem de locatários nas configurações

Armazene IDs de locatário permitidas na configuração para gerenciá-las sem alterações de código.

appsettings.json:

{
  "AzureAd": {
    "Instance": "https://login.microsoftonline.com/",
    "ClientId": "your-api-client-id",
    "AllowedTenants": [
      "14c2f153-90a7-4689-9db7-9543bf084dad",
      "af8cc1a0-d2aa-4ca7-b829-00d361edb652"
    ]
  }
}

Leia a lista de locatários e crie a política na inicialização:

var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
    .Get<string[]>();

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
    {
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants ?? Array.Empty<string>()
        );
    });
});

Combinar escopos com filtragem de cliente

Crie uma política que exija um escopo válido e um locatário aprovado:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("SecureApiAccess", policyBuilder =>
    {
        // Require specific scope
        policyBuilder.RequireScope("access_as_user");

        // AND require specific tenant
        policyBuilder.RequireClaim(
            "http://schemas.microsoft.com/identity/claims/tenantid",
            allowedTenants
        );
    });
});

Seguir as práticas recomendadas

Aplique essas recomendações para criar uma lógica de autorização segura e mantenedível.

O que fazer

1. Sempre associe [Authorize] com a validação de escopo:

[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }

2. Use a configuração para escopos específicos do ambiente:

[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]

3. Aplicar privilégios mínimos:

[HttpGet]
[RequiredScope("read")] // Only read permission needed

[HttpPost]
[RequiredScope("write")] // Write permission for modifications

4. Use políticas para autorização complexa:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AdminOnly", policy =>
    {
        policy.RequireScope("admin");
        policy.RequireClaim("department", "IT");
    });
});

5. Habilitar respostas de erro detalhadas no desenvolvimento:

if (builder.Environment.IsDevelopment())
{
    Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}

O que não fazer

1. Não ignore [Authorize] ao usar RequiredScope:

//  Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

//  Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }

2. Não codificar fixamente IDs de inquilino na produção:

//  Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");

//  Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);

3. Não confunda escopos com funções:

//  Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope

//  Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role

4. Não exponha informações confidenciais de escopo em mensagens de erro de produção:

Configure os níveis de log apropriados e o tratamento de erros para ambientes de produção.


Solucionar problemas de autorização

Use as diretrizes a seguir para diagnosticar problemas comuns de autorização.

403 Proibido – escopo ausente

Erro: A API retorna 403 mesmo com um token válido.

Diagnóstico:

  1. Decodificar o token em jwt.ms.
  2. Verifique scp ou scope declaração.
  3. Verifique se o valor corresponde ao atributo RequiredScope .

Solution:

  • Verifique se o aplicativo cliente solicita o escopo correto ao adquirir o token.
  • Verifique se o escopo está exposto no registro do aplicativo de API no Microsoft Entra.
  • Conceda consentimento do administrador, se necessário.

RequiredScope não está funcionando

Sintoma: O atributo parece ser ignorado.

Verificar:

  1. Você adicionou o [Authorize] atributo?
  2. Depois app.UseAuthorization(), é app.UseAuthentication() chamado?
  3. Está services.AddAuthorization() registrado?

Chave de configuração não encontrada

Erro: A validação de escopo falha silenciosamente.

Verificar:

{
  "AzureAd": {
    "Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
  }
}

Verifique se o caminho de configuração corresponde exatamente.