← ASP.NET Core: aplicações, contratos e operação
07 / 10 · 60 MIN

Autenticação e autorização

Separa validação da identidade, seleção da política e decisão sobre o recurso antes de divulgar dados.

Identidade e permissão

Autenticação transforma credenciais aceites numa identidade utilizável pela aplicação. Autorização decide se essa identidade satisfaz as condições de uma operação. Um access token validado pode identificar uma pessoa sem lhe dar acesso a todos os documentos.

Começa o diagnóstico por separar a falha de validação da credencial da falta de permissão. Num esquema JWT bearer explícito, challenge indica 401; forbid para uma identidade autenticada sem acesso indica 403.

Confiança no token

O texto de um JWT pode ser decodificado sem provar confiança. A API precisa de validar assinatura, emissor, audience e validade temporal segundo a configuração estabelecida. Authority e Audience devem corresponder ao emissor e ao recurso pretendidos, com metadados e chaves obtidos pelo mecanismo de confiança configurado.

Não uses um nome legível ou o owner do corpo para substituir essa validação. Depois de validar, confirma também os nomes das claims que a política realmente lê.

Selecionar a política efetiva

RequireAuthorization sem política específica usa a DefaultPolicy. Uma política nomeada seleciona os seus próprios requisitos. A FallbackPolicy configurada cobre endpoints sem metadata de autorização; não é uma lista de condições automaticamente acrescentada a toda a política nomeada.

Se Read deve exigir autenticação, permission=read e Admin, compõe essas condições explicitamente. Inspeciona também AllowAnonymous: o middleware pode autenticar, mas não impõe a falha de autorização nesse endpoint.

Requisitos e alternativas

Dentro de uma política, todos os requisitos precisam de ser satisfeitos. Dois handlers podem representar caminhos alternativos para o mesmo requisito, como pertença à equipa ou delegação válida. Quando um caminho não se aplica, deixar o requisito pendente permite avaliar a alternativa.

Chamar Fail é mais forte: impede sucesso global mesmo que outro handler chame Succeed. Não confundas vários requisitos combinados por AND com vários handlers que podem satisfazer um só requisito.

Estado e ordem dos handlers

A ordem de execução dos handlers não deve ser usada como contrato funcional. Se B precisa de dados que A guarda num campo mutável, uma alteração na composição pode mudar a decisão. Obtém as premissas através de dependências explícitas e evita guardar decisões específicas de um utilizador num singleton.

O handler do exemplo é stateless e avalia o principal e o recurso recebidos. Também não presume que o simples facto de ser chamado prova autenticação bem-sucedida.

Propriedade do recurso

A propriedade persistida do documento é uma premissa de autorização diferente de um campo OwnerId enviado pelo cliente. Carrega o documento da origem autorizada, avalia AuthorizeAsync com o principal e esse recurso e só depois constrói a resposta que divulga o conteúdo.

No exemplo, sub mantém o nome original porque a configuração assume MapInboundClaims=false. Numa aplicação com outro mapeamento, ajusta explicitamente o contrato de identidade em vez de comparar claims por tentativa.

Decidir antes de divulgar

Um 403 posterior não recupera bytes que já chegaram ao cliente. Mantém a decisão de acesso antes da serialização e do envio do documento. O cenário user-a a pedir o documento de user-b permite discutir a fronteira: preencher OwnerId=user-a no corpo não altera a propriedade armazenada.

A aplicação ainda precisa de escolher se distingue recurso inexistente de recurso proibido e de manter esse contrato consistente, sem expor conteúdo protegido no erro ou nos registos.

// ASP.NET Core 10 authorization excerpt, not a complete host.
// Prerequisites: JWT bearer is configured and validates trusted issuer,
// audience, signature and lifetime. MapInboundClaims=false preserves "sub".
// Register policy and handler before building the host:
// services.AddAuthorization(options => options.AddPolicy("DocumentOwner",
//     policy => policy.RequireAuthenticatedUser()
//                     .AddRequirements(new DocumentOwnerRequirement())));
// services.AddSingleton<IAuthorizationHandler, DocumentOwnerHandler>();
// Invoke ReadDocument with a document loaded from authoritative storage.
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Http;

public sealed record Document(int Id, string OwnerId, string Content);
public sealed class DocumentOwnerRequirement : IAuthorizationRequirement { }

public sealed class DocumentOwnerHandler
    : AuthorizationHandler<DocumentOwnerRequirement, Document>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context,
        DocumentOwnerRequirement requirement, Document resource)
    {
        string? subject = context.User.FindFirst("sub")?.Value;
        if (context.User.Identity?.IsAuthenticated == true &&
            subject is not null && subject == resource.OwnerId)
        {
            context.Succeed(requirement);
        }
        return Task.CompletedTask;
    }
}

public static class DocumentResponse
{
    public static async Task<IResult> ReadDocument(
        ClaimsPrincipal user, Document storedDocument,
        IAuthorizationService authorization)
    {
        var decision = await authorization.AuthorizeAsync(
            user, storedDocument, "DocumentOwner");
        if (decision.Succeeded)
            return Results.Ok(new { storedDocument.Id, storedDocument.Content });
        return user.Identity?.IsAuthenticated == true
            ? Results.Forbid() : Results.Challenge();
    }
}
NA PRÁTICA

Lê o excerto assumindo bearer como esquema de challenge e forbid, um único principal selecionado e sub sem remapeamento. Para Document(42,"user-b",...), sub=user-a não satisfaz DocumentOwner; sub=user-b autenticado satisfaz. O chamador da função carrega o recurso persistido e trata separadamente ausência do documento.

O excerto não configura JWT, repositório, endpoint ou contrato de ocultação da existência do recurso.

Armadilhas comuns

Decodificar um JWT não valida a sua assinatura. AllowAnonymous pode tornar o endpoint público. Uma política nomeada não herda a fallback. Comparar um owner fornecido pelo chamador permite que esse chamador escolha a premissa de autorização.

Tópicos relacionados: Rotas, binding e validação · Âmbito de testes e evidência de integração

Leva esta ideia contigo

Valida a identidade através do esquema configurado, identifica os requisitos aplicáveis e decide sobre o recurso real antes de produzir a resposta. Mantém explícito o contrato para 401, 403 e ausência do recurso.

Criar conta

Referência: Authentication identity construction · ASP.NET Core 10.0 / .NET 10 / C# 14