Autorização baseada em política no ASP.NET Core

Uma política de autorização ASP.NET Core é um conjunto nomeado de um ou mais requisitos de autorização que a estrutura avalia para decidir se um usuário tem permissão para acessar um recurso.

Este artigo explica:

  • Como criar requisitos.
  • Como registrar e aplicar políticas.
  • Manipuladores de autorização para avaliação de requisitos únicos e múltiplos.
  • Como vários requisitos em uma única política são avaliados.

Na prática, uma política é aplicada a [Authorize(Policy = "...")] (Razor componentes, páginas e controladores) ou RequireAuthorization(...) (endpoints), e o framework usa manipuladores para avaliar os requisitos de uma política. IAuthorizationPolicyProvider(Provedores de política de autorização personalizados na documentação ASP.NET Core) geram políticas dinamicamente em vez de registrá-las na inicialização do aplicativo.

Autorização baseada em função e autorização baseada em declarações usam um requisito, um manipulador de requisitos e uma política de autorização pré-configurada. Esses blocos de construção dão suporte à expressão de avaliações de autorização no código.

Este artigo usa exemplos de componentes Razor e se concentra em cenários de autorização Blazor para o ASP.NET Core 3.1 ou versão posterior. Para Razor obter as diretrizes de Páginas e MVC que se aplicam a todas as versões de ASP.NET Core, consulte os seguintes recursos depois de ler este artigo:

Alguns exemplos neste artigo (ASP.NET Core 8.0 ou posterior) usam construtores primários, disponíveis no C# 12 (.NET 8) ou posterior. Para obter mais informações, consulte Declarar construtores primários para classes e structs (tutorial de documentação do C#) e construtores primários (Guia do C#).

Requisitos e registro de política

Uma política de autorização consiste em um ou mais requisitos, usados para avaliar a autorização da identidade do usuário atual. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia.

Quando um requisito não contém dados ou tem propriedades (parâmetros), ele atua como um marcador vazio para disparar um manipulador de autorização associado (IAuthorizationHandler) para a autorização de processamento (descrito em detalhes mais adiante neste artigo). Como o manipulador nesse caso depende inteiramente do contexto HTTP, das declarações do usuário ou dos dados de back-end para tomar uma decisão sobre o usuário que atende ao requisito, a própria classe de requisito não exige dados ou parâmetros internos. O requisito apenas informa ao framework qual regra avaliar.

Por exemplo, considere o seguinte requisito de idade mínima (MinimumAgeRequirement), que é implementado apenas como uma classe de marcador:

public class MinimumAgeRequirement : IAuthorizationRequirement { }

O requisito anterior é usado para criar uma política que confirme que o usuário está acima de uma idade específica que o manipulador verifica. Um AuthorizationHandler<MinimumAgeRequirement> inspeciona o AuthorizationHandlerContext.User. Se o usuário tiver uma declaração de data de nascimento que indique que ele tenha mais de uma determinada idade, o requisito será bem-sucedido. O objeto de requisito não requer nenhuma propriedade (parâmetros) nesse caso. O exemplo a seguir demonstra a implementação completa de um requisito de idade mínima que tem um parâmetro para definir a idade mínima.

Considere o seguinte MinimumAgeRequirement requisito, que descreve um único parâmetro, uma idade mínima, a ser avaliado para autorização do usuário:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class MinimumAgeRequirement(int minimumAge) : IAuthorizationRequirement
{
    public int MinimumAge { get; } = minimumAge;
}
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public MinimumAgeRequirement(int minimumAge) =>
        MinimumAge = minimumAge;

    public int MinimumAge { get; }
}
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeRequirement : IAuthorizationRequirement
{
    public int MinimumAge { get; }

    public MinimumAgeRequirement(int minimumAge)
    {
        MinimumAge = minimumAge;
    }
}

Uma política é registrada como parte da configuração do serviço de autorização no arquivo Program do aplicativo ao chamar AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos.

builder.Services.AddAuthorizationBuilder()
    .AddPolicy("AtLeast21", policy => 
        policy.Requirements.Add(new MinimumAgeRequirement(21)));

Uma política é registrada como parte da configuração do serviço de autorização no arquivo Program do aplicativo ao chamar AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos:

builder.Services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Uma política é registrada como parte da configuração do serviço de autorização em Startup.ConfigureServices (Startup.cs) chamando AuthorizationBuilder.AddPolicy. O exemplo a seguir cria uma AtLeast21 política com um único requisito de idade mínima e define a idade mínima para 21 anos:

services.AddAuthorization(options =>
{
    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
});

Se uma política de autorização contiver vários requisitos de autorização, todos os requisitos deverão ser aprovados para que a avaliação da política seja bem-sucedida. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados com base em AND .

Aplicar políticas aos Razor componentes

Aplique políticas a Razor componentes usando o [Authorize] atributo com o nome da política:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]

Se várias políticas forem aplicadas, todas as políticas deverão ser aprovadas antes que o acesso seja concedido:

@using Microsoft.AspNetCore.Authorization
@attribute [Authorize(Policy = "CustomerServiceMember")]
@attribute [Authorize(Policy = "HumanResourcesMember")]

Aplicar políticas a pontos de extremidade

Aplique políticas aos pontos de extremidade usando RequireAuthorization com o nome da política. Por exemplo:

app.MapGet("/helloworld", () => "Hello World!")
    .RequireAuthorization("AtLeast21");

Aplicar políticas em aplicativos MVC e Razor Pages

Para obter diretrizes sobre como aplicar políticas em Razor aplicativos Pages e MVC, consulte os seguintes recursos:

Interface do serviço de autorização (IAuthorizationService)

IAuthorizationService é responsável principalmente por determinar se a autorização é bem-sucedida quando uma IAuthorizationService.AuthorizeAsync sobrecarga é chamada:

  • AuthorizeAsync(ClaimsPrincipal user, object resource, IEnumerable<IAuthorizationRequirement> requirements): verifica se um usuário atende a um conjunto específico de requisitos de autorização para um recurso especificado.
  • AuthorizeAsync(ClaimsPrincipal user, object resource, string policyName): verifica se um usuário atende a uma política de autorização específica para um recurso especificado.

Se um recurso não for necessário para a avaliação da política, null será passado como recurso.

Os métodos anteriores retornam um AuthorizationResult encapsulado em um Task.

Cada um IAuthorizationHandler é responsável por verificar se os requisitos são atendidos por meio de IAuthorizationHandler.HandleAsync. A AuthorizationHandlerContext classe contém as informações de autorização usadas pela IAuthorizationHandler implementação. IAuthorizationRequirement é uma interface de marcador sem métodos que serve como o mecanismo para acompanhar se a autorização foi bem-sucedida. Quando AuthorizationHandlerContext.Succeed é chamado com IAuthorizationRequirement, a política é satisfeita:

context.Succeed(requirement);

Manipuladores de autorização

Um manipulador de autorização é responsável pela avaliação das propriedades de um requisito. O manipulador de autorização avalia os requisitos em relação a um AuthorizationHandlerContext fornecido para determinar se o acesso é permitido.

Um requisito pode ter vários manipuladores. Um manipulador pode herdar AuthorizationHandler<TRequirement>, onde TRequirement é o requisito a ser tratado. Como alternativa, um manipulador pode implementar IAuthorizationHandler diretamente para lidar com mais de um tipo de requisito.

Usar um manipulador para um requisito

O exemplo a seguir mostra uma relação um-para-um na qual um gerenciador de idade mínima gerencia uma única exigência.

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = 
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, MinimumAgeRequirement requirement)
    {
        var dateOfBirthClaim = 
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth);

        if (dateOfBirthClaim is null)
        {
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(dateOfBirthClaim.Value);
        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}
using System;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class MinimumAgeHandler : AuthorizationHandler<MinimumAgeRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
        MinimumAgeRequirement requirement)
    {
        if (!context.User.HasClaim(c => c.Type == ClaimTypes.DateOfBirth))
        {
            // Use the following if targeting a version of
            // .NET Framework older than 4.6:
            // return Task.FromResult(0);
            return Task.CompletedTask;
        }

        var dateOfBirth = Convert.ToDateTime(
            context.User.FindFirst(c => c.Type == ClaimTypes.DateOfBirth).Value);

        var calculatedAge = DateTime.Today.Year - dateOfBirth.Year;

        if (dateOfBirth > DateTime.Today.AddYears(-calculatedAge))
        {
            calculatedAge--;
        }

        if (calculatedAge >= requirement.MinimumAge)
        {
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

O código anterior determina se o principal do usuário atual tem uma declaração de data de nascimento. A autorização não pode ocorrer quando a reivindicação está ausente, nesse caso, uma tarefa concluída é retornada. Quando uma declaração está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização será considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocado com o requisito atendido como seu único parâmetro.

Usar um gerenciador para vários requisitos

O exemplo a seguir mostra uma relação um-para-muitos na qual um manipulador de permissões pode lidar com três tipos diferentes de requisitos:

using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource)
                    || IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission || requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        return Task.CompletedTask;
    }

    private static bool IsOwner(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }

    private static bool IsSponsor(ClaimsPrincipal user, object? resource)
    {
        // Code omitted for brevity
        return true;
    }
}
using System.Linq;
using System.Security.Claims;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class PermissionHandler : IAuthorizationHandler
{
    public Task HandleAsync(AuthorizationHandlerContext context)
    {
        var pendingRequirements = context.PendingRequirements.ToList();

        foreach (var requirement in pendingRequirements)
        {
            if (requirement is ReadPermission)
            {
                if (IsOwner(context.User, context.Resource) ||
                    IsSponsor(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
            else if (requirement is EditPermission ||
                        requirement is DeletePermission)
            {
                if (IsOwner(context.User, context.Resource))
                {
                    context.Succeed(requirement);
                }
            }
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }

    private bool IsOwner(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }

    private bool IsSponsor(ClaimsPrincipal user, object resource)
    {
        // Code omitted for brevity

        return true;
    }
}

O código anterior varre PendingRequirements — uma propriedade que contém requisitos não marcados como concluídos com êxito. Para um requisito ReadPermission, o usuário deve ser o proprietário ou patrocinador para acessar o recurso solicitado. Para um requisito, seja EditPermission ou DeletePermission, eles devem ser proprietários para acessar o recurso solicitado.

Registro do manipulador

Registre manipuladores na coleção de serviços durante a configuração. O exemplo a seguir registra um manipulador de idade mínima (MinimumAgeHandler) como um serviço singleton, mas um manipulador pode ser registrado usando qualquer um dos tempos de vida de serviço internos:

builder.Services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();
services.AddSingleton<IAuthorizationHandler, MinimumAgeHandler>();

É possível agrupar um requisito e um manipulador em uma única classe implementando IAuthorizationRequirement e IAuthorizationHandler. Esse agrupamento cria um acoplamento apertado entre o manipulador e o requisito e é recomendado apenas para requisitos e manipuladores simples. A criação de uma classe que implementa ambas as interfaces elimina a necessidade de registrar o manipulador no contêiner de serviços devido ao PassThroughAuthorizationHandler interno, que permite que os requisitos tratem a si próprios.

Consulte a implementação da classe AssertionRequirement do ASP.NET Core como exemplo de um caso em que o AssertionRequirement é ao mesmo tempo um requisito e o manipulador em uma classe completamente autocontida. A API do framework AssertionRequirement permite validar o acesso por meio de expressões lambda inline, em vez de escrever classes separadas de requisito e de manipulador repetitivas.

Note

Os links da documentação para o código-fonte de referência do .NET geralmente carregam a ramificação padrão do repositório, que representa o estágio atual de desenvolvimento da próxima versão do .NET. Para selecionar uma marca para uma versão específica, use a lista suspensa para Alternar branches ou marcas. Para obter mais informações, consulte Como selecionar uma marca de versão do código-fonte ASP.NET Core (dotnet/AspNetCore.Docs #26205).

O que um manipulador deve retornar?

O Handle método no exemplo do manipulador não retorna nenhum valor. Como um status de êxito ou falha é indicado?

  • Um manipulador indica êxito chamando context.Succeed, passando o requisito validado com êxito (IAuthorizationRequirement).

  • Um manipulador não precisa tratar falhas de modo geral, pois outros manipuladores relativos ao mesmo requisito podem ter êxito.

  • Para garantir a falha, mesmo que outros manipuladores de requisitos tenham êxito, chame context.Fail.

Se um manipulador chamar context.Succeed ou context.Fail, todos os outros manipuladores ainda serão chamados. Isso permite que os requisitos produzam efeitos colaterais, como gravação em log, que ocorre mesmo que outro manipulador valide com êxito ou falhe em relação a um requisito. Quando definida como false, a propriedade InvokeHandlersAfterFailure causa um curto-circuito na execução dos manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure tem como padrão true; nesse caso todos os manipuladores são chamados.

Note

Manipuladores de autorização são chamados mesmo se a autenticação falhar. Além disso, os manipuladores podem ser executados em qualquer ordem, portanto não dependa da ordem de chamada dos manipuladores.

Por que iria querer vários manipuladores para um requisito?

Nos casos em que você deseja que a avaliação seja baseada em OR , implemente vários manipuladores para um único requisito. Por exemplo, suponha que a Contoso Corporation tenha portas que só abrem com cartões-chave. Se você deixar seu cartão de chave em casa, a recepcionista imprimirá um adesivo temporário e abrirá a porta para você. Nesse cenário, o aplicativo tem um único requisito, mas vários manipuladores, cada um examinando um único requisito.

Nas seguintes implementações de exemplo:

  • BuildingEntryRequirement é o requisito de acesso ao edifício.
  • BadgeEntryHandler (o indivíduo tem um selo) e TemporaryStickerHandler (o indivíduo tem um adesivo temporário) são manipuladores separados, cada um examinando um único requisito.

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

namespace BlazorWebAppAuthorization.Policies.Requirements;

public class BuildingEntryRequirement : IAuthorizationRequirement { }

BadgeEntryHandler.cs:

using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => c.Type == "BadgeId"))
        {
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs:

using Microsoft.AspNetCore.Authorization;
using BlazorWebAppAuthorization.Policies.Requirements;

namespace BlazorWebAppAuthorization.Policies.Handlers;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(
        AuthorizationHandlerContext context, BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "TemporaryBadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            // Code to check expiration date omitted for brevity.
            context.Succeed(requirement);
        }

        return Task.CompletedTask;
    }
}

BuildingEntryRequirement.cs:

using Microsoft.AspNetCore.Authorization;

public class BuildingEntryRequirement : IAuthorizationRequirement
{
}

BadgeEntryHandler.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class BadgeEntryHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,
                                                    BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "BadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

TemporaryStickerHandler.cs:

using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;

public class TemporaryStickerHandler : AuthorizationHandler<BuildingEntryRequirement>
{
    protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, 
        BuildingEntryRequirement requirement)
    {
        if (context.User.HasClaim(c => 
            c.Type == "TemporaryBadgeId" &&
            c.Issuer == "https://contososecurity"))
        {
            // We'd also check the expiration date on the sticker.
            context.Succeed(requirement);
        }

        // Use the following if targeting a version of
        // .NET Framework older than 4.6:
        // return Task.FromResult(0);
        return Task.CompletedTask;
    }
}

Verifique se ambos os manipuladores estão registrados. Se qualquer um dos manipuladores for bem-sucedido quando uma política avaliar o BuildingEntryRequirement, a avaliação da política será bem-sucedida.

Usar um Func para atender a uma política

Há situações em que o cumprimento de uma política é simples de expressar no código com um Func<AuthorizationHandlerContext, bool> delegado ao configurar uma política com o RequireAssertion construtor de políticas. Por exemplo, o anterior BadgeEntryHandler pode ser reescrito da seguinte maneira:

    options.AddPolicy("AtLeast21", policy =>
        policy.Requirements.Add(new MinimumAgeRequirement(21)));
            (c.Type == "BadgeId" || c.Type == "TemporaryBadgeId")
            && c.Issuer == "https://contososecurity")));
});

// <snippet_minimumAgeHandlerRegistration>
services.AddAuthorization(options =>
{
     options.AddPolicy("BadgeEntry", policy =>
        policy.RequireAssertion(context =>
            context.User.HasClaim(c =>
                (c.Type == "BadgeId" ||
                 c.Type == "TemporaryBadgeId") &&
                 c.Issuer == "https://microsoftsecurity")));
});

Exigir autenticação de usuário global

Para obter informações sobre como exigir autenticação para todos os usuários do aplicativo, consulte Criar um aplicativo ASP.NET Core com dados do usuário protegidos por autorização.

Autorização por meio de um exemplo de serviço externo

A Autorização por meio de um exemplo de serviço externo (dotnet/AspNetCore.Docs.SamplesGitHub repositório) mostra como implementar requisitos de autorização adicionais com um serviço de autorização externo. A solução Contoso.API tem seu projeto protegido por Microsoft Entra ID. Uma verificação de autorização adicional do Contoso.Security.API project retorna uma carga que descreve se o aplicativo cliente Contoso.API pode invocar a API de GetWeather.

Configurar o exemplo

A demonstração a seguir depende do uso de NSwag (Swagger/OpenAPI) ou cURL em um shell de comando.

No projeto Contoso.Security.API, defina o marcador AllowedClients ({CLIENT ID}) como qualquer valor de teste de GUID (por exemplo, 00001111-aaaa-2222-bbbb-3333cccc4444):

{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "AllowedClients": [
    "{CLIENT ID (FOR THE CLIENT CALLING CONTOSO.API)}"
  ]
}

Em um shell de comando aberto ao Contoso.API projeto, use dotnet user-jwts para gerar um token de acesso com uma declaração appid para a ID do aplicativo cliente, que foi criada na etapa anterior (por exemplo, 00001111-aaaa-2222-bbbb-3333cccc4444).

dotnet user-jwts create --claim appid={GUID}

Exemplo:

dotnet user-jwts create --claim appid=00001111-aaaa-2222-bbbb-3333cccc4444

O resultado produz um token após "Token:" no console de comando.

New JWT saved with ID '{JWT ID}'.
Name: {USER}
Custom Claims: [appid=00001111-aaaa-2222-bbbb-3333cccc4444]

Token: {TOKEN}

Defina o valor do token (onde o marcador {TOKEN} aparece na saída anterior) para uso posterior.

Você pode decodificar o token em um decodificador online JWT , como jwt.ms ver seu conteúdo, revelando que ele contém uma declaração appid com a ID do aplicativo cliente:

{
  "alg": "HS256",
  "typ": "JWT"
}.{
  "unique_name": "{USER}",
  "sub": "{USER}",
  "jti": "14ed7729",
  "appid": "{CLIENT ID}",
  "aud": [
    "https://localhost:7250",
    "http://localhost:7251"
  ],
  "nbf": 1780660887,
  "exp": 1788609687,
  "iat": 1780660888,
  "iss": "dotnet-user-jwts"
}.[Signature]

Execute o comando novamente com um valor de IDappid do cliente () incorreto:

dotnet user-jwts create --claim appid=aaaabbbb-0000-cccc-1111-dddd2222eeee

Defina o valor do segundo token à parte.

Inicie ambos os projetos Contoso.API e Contoso.Security.API no Visual Studio ou com o comando dotnet watch em um shell de comando:

dotnet watch

Na interface do Swagger do projeto Contoso.API (https://localhost:7250/swagger/index.html), selecione o botão Autorizar.

Nas autorizações disponíveis: Bearerjanela, insira o token de acesso. Selecione o botão Autorizar. Feche a janela de autorizações disponíveis .

Em default, selecione o botão Get para o endpoint /WeatherForecast. Selecione o botão Experimentar . Selecione o botão Executar .

A saída em Responses>Resposta do servidor>Corpo da resposta mostra o JSON de previsão do tempo retornado pelo Contoso.API projeto.

Execute as mesmas etapas com o token de acesso que foi gerado com uma ID de aplicativo cliente inválida. A resposta é 403 – Proibido.

Recursos adicionais