ASP.NET Core - Política de Autorização baseada em Claims - I


  Hoje veremos como usar a política de autorização baseada em Claims da ASP.NET Core Identity.

A autorização é um processo de determinar se o usuário é capaz de acessar os recursos do sistema.  A ASP.NET Core Identity permite mapear uma ou mais funções(roles) com o usuário; com base na função(role), podemos fazer a autorização.  Além disso podemos usar as claims ou declarações para definir políticas de autorização.

Autorização baseada em Claims

As claims ou declarações são informações sobre um usuário que são emitidas por uma fonte confiável. Se estivermos trabalhando com autenticação baseada em token, uma claim pode ser adicionada dentro de um token pelo servidor que gera o token.

Uma claim pode ter qualquer tipo de dados como "DataDeEntrada", "DataDeNascimento", "email", etc. Com base na claim que um usuário possui, um sistema fornece o acesso à página, que é chamado de Autorização baseada em Claim. Por exemplo, o sistema fornecerá acesso à página, se o usuário tiver uma claim "DataNascimento".

Resumindo, a autorização baseada em claims verifica o valor das claims e permite o acesso ao recurso do sistema com base no valor de uma ou mais claims.

A classe Claim esta presente no namespace System.Security.Claims, e contém um objeto IDictionary,
onde podemos criar uma declaração com tipo e valor string type, string value.

A seguir temos um trecho de código que mostra como criar uma lista de claims:

using System.Security.Claims;
var claims = new List<Claim> 
{
        new Claim(ClaimTypes.Country, "Brasil", ClaimValueTypes.String, Issuer),
        new Claim("CorFavorita", "Azul", ClaimValueTypes.String)
};	

Para ilustrar como criar claims e definir uma politica de autorização com base nelas vamos criar um projeto ASP.NET Core MVC  chamado MvcClaimsPolicyAutorization no VS 2022 usando o template padrão e selecionando a opção Individual Accounts na opção Authentication Type.

Com isso teremos um projeto já configurado para usar o Identity onde o código da classe Program deve conter as seguintes definições:

...

var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") 
        ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");

builder.Services.AddDbContext<ApplicationDbContext>(options =>
       options.UseSqlServer(connectionString));

builder.Services.AddDatabaseDeveloperPageExceptionFilter();

builder.Services.AddIdentity<IdentityUser, IdentityRole>()
      .AddEntityFrameworkStores<ApplicationDbContext>()
      .AddDefaultUI();
...

app.UseAuthentication();
app.UseAuthorization();
...
app.Run();

Você pode aplicar o Migrations usando os comandos : add-migration Inicial e a seguir update-database (Se executar a aplicação tentar registrar um usuário a migração pendente será aplicada)

A seguir vamos criar um Controlador chamado DemoController na pasta Controllers com o código a seguir:

public class DemoController : Controller
{
  
public IActionResult ActionMetodo1()
   {
     
return View("MinhaPagina");
   }
}

A seguir vamos criar a view MinhaPagina.cshtml com o código abaixo:

<div class="jumbotron jumbotron-fluid">
   <
div class="container">
       <
h1 class="display-4">Acesso Concedido</h1>
        <
p class="lead">Bem-vindo a minha página.</p>
   </
div>
</
div>

Vamos criar no projeto a pasta SeedUsersClaims onde vamos criar a interface ISeedUserClaim com o seguinte código :

public interface ISeedUserClaim
{
   Task SeedUsersClaims();
}

A seguir vamos criar nesta pasta a classe concreta SeedUserClaim onde vamos criar dois usuários :

1- Usuário 1-  Nome e email igual a admin@yahoo.com e senha : 'Numsey#2023'

Para este usuário vamos criar duas claims:

  1. CadastradoEm =  '09/05/2014'
  2. IsAdmin com valor igual a true

2- Usuário 2 - Nome e email igual a usuario@yahoo.com e senha : 'Numsey#2023'

Para este usuário vamos criar a claim:

  1. IsAdmin com valor igual a false

Para fazer isso vamos definir o seguinte código na classe SeedUserClaim:

using Microsoft.AspNetCore.Identity;
using System.Security.Claims;
namespace MvcClaimsPolicyAutorization.SeedUsersClaims;

public class SeedUserClaim : ISeedUserClaim
{
    private readonly UserManager<IdentityUser> _userManager;

    public SeedUserClaim(UserManager<IdentityUser> userManager)=> 
                                                  _userManager = userManager;
    public async Task SeedUsersClaims()
    {
        try
        {
            //Cria usuário 1
            IdentityUser user1 =  await _userManager.FindByEmailAsync("admin@yahoo.com");
            if (user1 == null)
            {
                user1 = new IdentityUser()
                {
                    UserName = "admin@yahoo.com",
                    Email = "admin@yahoo.com",
                };
                IdentityResult result = await _userManager.CreateAsync(user1, "Numsey#2023");
                if (result.Succeeded)
                {
                    var claimList = (await _userManager.GetClaimsAsync(user1))
                                .Select(p => p.Type);
                    if (!claimList.Contains("CadastradoEm"))
                    {
                        var claimResult1 = await _userManager.AddClaimAsync(user1, 
                                 new Claim("CadastradoEm", "09/15/2014"));
                    }
                    if (!claimList.Contains("IsAdmin"))
                    {
                        var claimResult2 = await _userManager.AddClaimAsync(user1,
                                 new Claim("IsAdmin", "true"));
                    }
                }
            }
            //Cria usuário 2
            IdentityUser user2 = await _userManager.FindByEmailAsync("usuario@gmail.com");
            if (user2 == null)
            {
                user2 = new IdentityUser()
                {
                    UserName = "usuario@gmail.com",
                    Email = "usuario@gmail.com",
                };
                IdentityResult result = await _userManager.CreateAsync(user2, "Numsey#2023");
                if (result.Succeeded)
                {
                    var claimList = (await _userManager.GetClaimsAsync(user2)).Select(p => p.Type);
                    if (!claimList.Contains("IsAdmin"))
                    {
                        var claimResult = await _userManager.AddClaimAsync(user2,
                                               new Claim("IsAdmin", "false"));
                    }
                }
            }
        }
        catch (Exception ex)
        {
            throw;
        }
    }
}

Agora vamos registrar o serviço da interface ISeedUserClaim na classe Program e criar o método CriarUsuariosClaims() que vai invocar o serviço para criar os dois usuários e as suas claims:

...

builder.Services.AddScoped<ISeedUserClaim, SeedUserClaim>();

var app = builder.Build();

...

await CriarUsuariosClaims(app);

app.Run();

async Task CriarUsuariosClaims(WebApplication app)
{
  
var scopedFactory = app.Services.GetService<IServiceScopeFactory>();
  
using (var scope = scopedFactory.CreateScope())
   {
     
var service = scope.ServiceProvider.GetService<ISeedUserClaim>();
     
await service.SeedUsersClaims();
   }
}

Após executar a aplicação este método será invocado e teremos nas tabelas do Identity AspNetUsers e AspNetUserClaims preenchidas com os dois usuário e suas claims conforme mostrado a seguir:

1- AspNetUsers

2- AspNetUserClaims



Com isso podemos definir políticas de autorização com base nestas informações do usuário.

A autorização baseada em claim pode ser feita criando uma política; ou seja, criar e registrar a política declarando a exigência de claims ou reivindicações.

O tipo de política de claim simples verifica apenas a existência da claim, mas com nível avançado, podemos verificar a claim do usuário com seu valor. Também podemos atribuir mais de um valor para uma verificação de claim.

Para ilustrar vamos criar duas políticas para verificar as claims de autorização dos usuários que foram criados.

  1. Na primeira vamos verificar a claim CadastradoEm fazendo um verificação simples da claim, ou seja, verificando se ela existe ou não;
  2. Na segunda vamos verificar o valor da claim IsAdmin se ela é true ou false;

Para isso vamos incluir na classe Program o código abaixo:

...

builder.Services.AddAuthorization(options =>
{
   options.AddPolicy(
"IsAdminClaimAccess", policy => policy.RequireClaim("CadastradoEm"));
   options.AddPolicy(
"IsAdminClaimAccess", policy => policy.RequireClaim("IsAdmin", "true"));
});
...

Agora podemos aplicar esta política ao atributo Authorize usando a propriedade "Policy" onde temos que especificar o nome da política.

Podemos definir o atributo Authorize seguido da nome da política no controlador ou nos métodos Action:

using Microsoft.AspNetCore.Authorization;
using
Microsoft.AspNetCore.Mvc;

namespace MvcClaimsPolicyAutorization.Controllers;

public class DemoController : Controller
{

   [Authorize(Policy = "IsAdminClaimAccess")]
  
public IActionResult ActionMetodo1()
   {
     
return View("MinhaPagina");
   }
}

Agora somente o usuário admin@yahoo.com que possui a claim CadastradoEm e a claim IsAdmin com valor igual a true e que poderá acessar MinhaPagina. O usuário usuario@gmail.com não possui a claim CadastradoEm a a sua claim IsAdmin é igual a false.

Executando o projeto podemos conferir o comportamento conforme abaixo:

Também podemos aplicar várias políticas ao controlador ou Action onde para conceder acesso, todas as políticas devem ser aprovadas.

Como alternativa, as claims também podem ser atribuídas a uma role ou função de um usuário, portanto, usando isso, um grupo inteiro de usuários pode acessar a página ou os recursos.

Na próxima parte do artigo vamos mostrar como usar esta abordagem.

Pegue o projeto aqui:  MvcClaimsPolicyAutorization.zip ...

"Celebrai com júbilo ao SENHOR, todas as terras.
Servi ao Senhor com alegria; e entrai diante dele com canto. "
Salmos 101:1-2

Referências:


José Carlos Macoratti