Autorização baseada em políticas 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 o framework avalia para decidir se um utilizador pode aceder a um recurso.

Este artigo explica:

  • Como criar requisitos.
  • Como registar e aplicar políticas.
  • Manipuladores de autorização para avaliação de requisitos únicos e múltiplos.
  • Como são avaliados múltiplos requisitos numa única apólice.

Na prática, uma política é aplicada com [Authorize(Policy = "...")] (Razor componentes, páginas e controladores) ou RequireAuthorization(...) (endpoints), e o framework utiliza handlers para avaliar os requisitos por detrás de uma política. IAuthorizationPolicyProvider(Fornecedores personalizados de políticas de autorização na documentação ASP.NET Core) gera políticas dinamicamente em vez de as registar no arranque da aplicação.

A autorização baseada em funções e a autorização baseada em reclamações utilizam um requisito, um gestor de requisitos e uma política de autorização pré-configurada. Esses blocos de construção suportam a expressão de avaliações de autorização no código.

Este artigo utiliza exemplos de componentes Razor e foca-se em cenários de autorização Blazor para ASP.NET Core 3.1 ou posteriores. Para orientações Razor Pages e MVC que se aplicam a todas as versões do ASP.NET Core, consulte os seguintes recursos após ler este artigo:

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

Requisitos e registo de apólices

Uma política de autorização consiste em um ou mais requisitos, que são usados por uma política para avaliar a autorização do principal utilizador atual. Um requisito implementa IAuthorizationRequirement, que é uma interface de marcador vazia.

Quando um requisito não contém dados ou não possui propriedades (parâmetros), atua como um marcador vazio para ativar um gestor de autorização associado (IAuthorizationHandler) para processamento de autorização (descrito em detalhe mais adiante neste artigo). Como o handler, neste caso, depende inteiramente do contexto HTTP, das reivindicações do utilizador ou dos dados do backend para tomar uma decisão sobre o utilizador cumprir o requisito, a própria classe de requisitos não requer dados ou parâmetros internos. O requisito apenas instrui a estrutura sobre qual regra deve avaliar.

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

public class MinimumAgeRequirement : IAuthorizationRequirement { }

O requisito anterior é usado para criar uma política que confirma que o utilizador tem mais de uma idade específica que o handler verifica. Um AuthorizationHandler<MinimumAgeRequirement> inspeciona o AuthorizationHandlerContext.User. Se o utilizador tiver uma alegação de data de nascimento que indique que tem mais de uma certa idade, o requisito é aprovado. O objeto requisito não requer quaisquer propriedades (parâmetros) neste caso. O exemplo seguinte 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 avaliar para autorização do utilizador:

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 é registada como parte da configuração do serviço de autorização no ficheiro da Program aplicação ao chamar AuthorizationBuilder.AddPolicy. O exemplo seguinte cria uma AtLeast21 apólice 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 é registada como parte da configuração do serviço de autorização no ficheiro da Program aplicação ao chamar AuthorizationBuilder.AddPolicy. O exemplo seguinte cria uma AtLeast21 apólice 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 é registada como parte da configuração do serviço de autorização em Startup.ConfigureServices (Startup.cs) ao chamar AuthorizationBuilder.AddPolicy. O exemplo seguinte cria uma AtLeast21 apólice 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 múltiplos requisitos de autorização, todos os requisitos devem ser aprovados para que a avaliação da apólice tenha sucesso. Em outras palavras, vários requisitos de autorização adicionados a uma única política de autorização são tratados com base em E .

Aplicar políticas aos Razor componentes

Aplicar políticas aos 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 devem ser aprovadas antes de o acesso ser concedido:

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

Aplicar políticas a pontos de extremidade

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

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

Aplicar políticas em aplicações MVC e de Razor Pages

Para orientações sobre como aplicar políticas nas Razor aplicações Pages e MVC, consulte os seguintes recursos:

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

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

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

Se um recurso não for necessário para a avaliação de políticas, null é passado para o recurso.

Os métodos anteriores retornam um AuthorizationResult envolto num Task.

Cada IAuthorizationHandler é responsável por verificar se os requisitos são cumpridos através de IAuthorizationHandler.HandleAsync. A AuthorizationHandlerContext classe contém a informação de autorização usada pela IAuthorizationHandler implementação. IAuthorizationRequirement é uma interface marcadora sem métodos que serve como mecanismo para acompanhar se a autorização é bem-sucedida. Quando AuthorizationHandlerContext.Succeed é chamado com o IAuthorizationRequirement, a política é cumprida:

context.Succeed(requirement);

Gestores de autorização

Um manipulador de autorização é responsável pela avaliação das propriedades de um requisito. O gestor 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>, em que 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 gestor de idade mínima gere 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 utilizador principal atual tem um pedido de data de nascimento. A autorização não pode ocorrer quando a declaração está ausente, caso em que uma tarefa concluída é retornada. Quando uma reivindicação está presente, a idade do usuário é calculada. Se o usuário atender à idade mínima definida pelo requisito, a autorização é considerada bem-sucedida. Quando a autorização é bem-sucedida, context.Succeed é invocado com o requisito satisfeito como seu único parâmetro.

Usar um manipulador 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 atravessa uma propriedade PendingRequirements, que contém requisitos não marcados como bem-sucedidos. Para um requisito ReadPermission, o utilizador deve ser proprietário ou patrocinador para aceder ao recurso solicitado. Para um requisito EditPermission ou DeletePermission, é necessário ser dono para aceder o recurso solicitado.

Registo do handler

Registre manipuladores na coleção de serviços durante a configuração. O exemplo seguinte regista um manipulador de idade mínima (MinimumAgeHandler) como um serviço singleton, mas um manipulador pode ser registado com qualquer um dos tempos de vida do serviço incorporados:

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

É possível agrupar um requisito e um manipulador em uma única classe que implemente ambos IAuthorizationRequirement e IAuthorizationHandler. Esse agrupamento cria um acoplamento estreito entre o manipulador e o requisito e só é recomendado para requisitos e manipuladores simples. Criar uma classe que implementa ambas as interfaces elimina a necessidade de registar o handler no contentor de serviço devido ao sistema incorporado PassThroughAuthorizationHandler que permite que os requisitos se controlem a si próprios.

Veja a implementação da classe ASP.NET Core AssertionRequirement para um exemplo onde o AssertionRequirement é simultaneamente um requisito e o handler numa classe totalmente autónoma. A AssertionRequirement API do framework permite-lhe validar o acesso usando expressões lambda inline em vez de escrever classes separadas de requisitos e handlers padrão.

Note

Os links de documentação para a fonte de referência .NET normalmente carregam a ramificação padrão do repositório, que representa o desenvolvimento atual para a próxima versão do .NET. Para selecionar uma tag para uma versão específica, use a lista suspensa Alternar entre ramificações ou tags. Para mais informações, consulte Como selecionar uma etiqueta 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 handler não devolve valor. Como é indicado um status de sucesso ou fracasso?

  • Um handler indica sucesso ao chamar context.Succeed, passando o requisito validado com sucesso (IAuthorizationRequirement).

  • Um handler geralmente não é obrigado a lidar com falhas, pois outros handlers para o mesmo requisito podem ter sucesso.

  • Para garantir falhas, mesmo que outros manipuladores de requisitos sejam bem-sucedidos, chame context.Fail.

Se um manipulador chamar context.Succeed ou context.Fail, todos os outros manipuladores ainda serão chamados. Isto permite que os requisitos produzam efeitos secundários, como o registo, que ocorre mesmo que outro manipulador valide ou falhe com sucesso um requisito. Quando definida como false, a propriedade InvokeHandlersAfterFailure curto-circuita a execução de manipuladores quando context.Fail é chamada. InvokeHandlersAfterFailure define-se como padrão true, caso em que todos os manipuladores são chamados.

Note

Os manipuladores de autorização são chamados mesmo quando a autenticação falha. Além disso, os manipuladores podem ser executados em qualquer ordem, por isso não dependa da ordem pela qual os manipuladores são chamados.

Por que gostaria de ter vários gestores 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 tem portas que só se abrem com cartões-chave. Se você deixar seu cartão-chave em casa, a rececionista imprime um adesivo temporário e abre a porta para você. Neste cenário, a aplicação tem um único requisito, mas vários handlers, cada um a examinar um único requisito.

Nos seguintes exemplos de implementações:

  • BuildingEntryRequirement é o requisito de entrada no edifício.
  • BadgeEntryHandler (o indivíduo tem um distintivo) e TemporaryStickerHandler (o indivíduo tem um autocolante temporário) são tratadores 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;
    }
}

Certifique-se de que ambos os manipuladores estejam registrados. Se algum dos manipuladores tiver sucesso quando uma política avalia o BuildingEntryRequirement, a avaliação da política tem sucesso.

Utilize um(a) Func para cumprir uma política

Existem situações em que cumprir uma política é simples de expressar em código com um Func<AuthorizationHandlerContext, bool> delegado ao configurar uma política com o RequireAssertion Policy Builder. Por exemplo, o anterior BadgeEntryHandler pode ser reescrito da seguinte forma:

    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 global do utilizador

Para informações sobre como exigir autenticação para todos os utilizadores da aplicação, consulte Crie uma aplicação ASP.NET Core com dados de utilizador protegidos por autorização.

Autorização através de um exemplo de serviço externo

A Autorização através de um exemplo de serviço externo (dotnet/AspNetCore.Docs.Samplesrepositório GitHub) mostra como implementar requisitos adicionais de autorização com um serviço de autorização externo. O projeto Contoso.API da solução está protegido com Microsoft Entra ID. Uma verificação adicional de autorização do Contoso.Security.API project devolve um payload que descreve se a aplicação cliente Contoso.API pode invocar a API GetWeather.

Configurar o exemplo

A demonstração seguinte baseia-se na utilização de NSwag (Swagger/OpenAPI) ou cURL numa shell de comandos.

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

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

Numa linha de comandos aberta no projeto Contoso.API, utilize dotnet user-jwts para gerar um token de acesso com uma declaração appid para o ID da aplicação cliente, que foi criado no passo 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

A saída produz um token após "Token:" na linha de comandos.

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

Token: {TOKEN}

Coloque o valor do token (onde o espaço reservado {TOKEN} aparece no resultado anterior) de lado para utilizar mais tarde.

Pode decodificar o token num decodificador online JWT , por exemplo jwt.ms , para ver o seu conteúdo, revelando que contém uma appid reclamação com o ID da aplicação 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 novamente o comando com um valor incorreto do ID do cliente (appid):

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

Ponha de lado o valor do segundo token.

Inicie ambos os projetos Contoso.API e Contoso.Security.API no Visual Studio ou com o comando dotnet watch numa linha de comandos:

dotnet watch

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

Na janela Autorizações Disponíveis, Bearer introduza o token de acesso. Selecione o botão Autorizar . Fecha a janela de Autorizações Disponíveis .

Em predefinição, selecione o botão Get para o ponto final /WeatherForecast. Selecione o botão Experimentar . Selecione o botão Executar .

O resultado em Respostas>Resposta do servidor>Corpo da resposta mostra o JSON da previsão meteorológica devolvido pelo projeto Contoso.API.

Execute os mesmos passos com o token de acesso gerado com um ID de aplicação cliente inválido. A resposta é 403 - Proibido.

Recursos adicionais