Compartilhar via


Mapeamento, personalização e transformação de declarações no ASP.NET Core

Por Damien Bowden

As declarações podem ser criadas a partir de qualquer dado de usuário ou identity que possa ser emitido usando um provedor de identity confiável ou identity do ASP.NET Core. Uma declaração é um par de valores de nome que representa o titular, não o que o titular pode fazer. Esse artigo aborda os seguintes tópicos:

  • Como configurar e mapear declarações usando um cliente do OpenID Connect
  • Definir o nome e a declaração de função
  • Redefinir os namespaces de declarações
  • Personalizar, estender as declarações por meio de TransformAsync

Como mapear declarações usando a autenticação do OpenID Connect

As declarações de perfil podem ser retornadas no id_token, que é retornado após uma autenticação bem-sucedida. O aplicativo cliente do ASP.NET Core requer apenas o escopo do perfil. Ao usar o id_token para declarações, nenhum mapeamento de declarações extra é necessário.

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

if (!app.Environment.IsDevelopment())
{
    app.UseExceptionHandler("/Error");
    app.UseHsts();
}

app.UseHttpsRedirection();
app.UseStaticFiles();

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

app.MapRazorPages();

app.Run();

O código anterior requer o pacote NuGet Microsoft.AspNetCore.Authentication.OpenIdConnect .

Outra maneira de obter as declarações do usuário é usar a API de Informações do Usuário do OpenID Connect. O aplicativo cliente do ASP.NET Core usa a propriedade GetClaimsFromUserInfoEndpoint para configurar isso. Uma diferença importante em relação às primeiras configurações é que você deve especificar as declarações necessárias por meio do método MapUniqueJsonKey, caso contrário, somente as declarações padrão name, given_name e email estarão disponíveis no aplicativo cliente. As declarações incluídas no id_token são mapeadas por padrão. Essa é a principal diferença para a primeira opção. Você deve definir explicitamente algumas das declarações necessárias.

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
       options.GetClaimsFromUserInfoEndpoint = true;
       options.ClaimActions.MapUniqueJsonKey("preferred_username",
                                             "preferred_username");
       options.ClaimActions.MapUniqueJsonKey("gender", "gender");
   });

var app = builder.Build();

// Code removed for brevity.

Observação

O manipulador padrão do Open ID Connect usa PAR (Solicitações de Autorização por Push) se o documento de descoberta do provedor de identity anunciar suporte para PAR. O documento de descoberta do provedor de identity geralmente é encontrado em .well-known/openid-configuration. Se você não puder usar o PAR na configuração do cliente no provedor de identity, o PAR poderá ser desabilitado usando a opção PushedAuthorizationBehavior.

builder.Services
    .AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect("oidc", oidcOptions =>
    {
        // Other provider-specific configuration goes here.

        // The default value is PushedAuthorizationBehavior.UseIfAvailable.

        // 'OpenIdConnectOptions' does not contain a definition for 'PushedAuthorizationBehavior'
        // and no accessible extension method 'PushedAuthorizationBehavior' accepting a first argument
        // of type 'OpenIdConnectOptions' could be found
        oidcOptions.PushedAuthorizationBehavior = PushedAuthorizationBehavior.Disable;
    });

Para garantir que a autenticação só seja bem-sucedida se o PAR for usado, use PushedAuthorizationBehavior.Require. Essa alteração também introduz um novo evento OnPushAuthorization para OpenIdConnectEvents, que pode ser usado para personalizar a solicitação de autorização por push ou tratá-la manualmente. Para ver mais detalhes, consulte a proposta da API.

Mapeamento de declaração de nome e declaração de função

A declaração Nome e a declaração Função são mapeadas para propriedades padrão no contexto HTTP ASP.NET Core. Às vezes, é necessário usar declarações diferentes para as propriedades padrão ou a declaração de nome e a declaração de função não correspondem aos valores padrão. As declarações podem ser mapeadas usando a propriedade TokenValidationParameters e definidas como qualquer declaração conforme necessário. Os valores das declarações podem ser usados diretamente na propriedade User.Identity.Name e nas funções.

Se o User.Identity.Name não tiver nenhum valor ou as funções estiverem ausentes, verifique os valores nas declarações retornadas e defina os valores NameClaimType e RoleClaimType. As declarações retornadas da autenticação do cliente podem ser exibidas no contexto HTTP.

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
  .AddCookie()
  .AddOpenIdConnect(options =>
  {
       // Other options...
       options.TokenValidationParameters = new TokenValidationParameters
       {
          NameClaimType = "email"
          //, RoleClaimType = "role"
       };
  });

Namespaces de declarações, namespaces padrão

O ASP.NET Core adiciona namespaces padrão a algumas declarações conhecidas, o que pode não ser necessário no aplicativo. Opcionalmente, desabilite esses namespaces adicionados e use as declarações exatas que o servidor OpenID Connect criou.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

JsonWebTokenHandler.DefaultInboundClaimTypeMap.Clear();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.
var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.

Se você precisar desabilitar os namespaces por esquema e não globalmente, poderá usar a opção MapInboundClaims = false.

var builder = WebApplication.CreateBuilder(args);

builder.Services.AddRazorPages();

builder.Services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.MapInboundClaims = false;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

var app = builder.Build();

// Code removed for brevity.

Estender ou adicionar declarações personalizadas por meio de IClaimsTransformation

A interface IClaimsTransformation pode ser usada para adicionar declarações extras à classe ClaimsPrincipal. A interface exige um único método TransformAsync. Esse método pode ser chamado várias vezes. Só adicione uma nova declaração se ela ainda não existir no ClaimsPrincipal. Um ClaimsIdentity é criado para adicionar as novas declarações e isso pode ser adicionado ao ClaimsPrincipal.

using Microsoft.AspNetCore.Authentication;
using System.Security.Claims;

public class MyClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
        ClaimsIdentity claimsIdentity = new ClaimsIdentity();
        var claimType = "myNewClaim";
        if (!principal.HasClaim(claim => claim.Type == claimType))
        {
            claimsIdentity.AddClaim(new Claim(claimType, "myClaimValue"));
        }

        principal.AddIdentity(claimsIdentity);
        return Task.FromResult(principal);
    }
}

A interface IClaimsTransformation e a classe MyClaimsTransformation podem ser registradas como um serviço:

builder.Services.AddTransient<IClaimsTransformation, MyClaimsTransformation>();

Mapear declarações de provedores de identity externos

Consulte a seguinte documentação:

Para saber mais, consulte Persistir declarações e tokens adicionais de provedores externos no ASP.NET Core.

As declarações podem ser criadas a partir de qualquer dado de usuário ou identity que possa ser emitido usando um provedor de identity confiável ou identity do ASP.NET Core. Uma declaração é um par de valores de nome que representa o titular, não o que o titular pode fazer. Esse artigo aborda os seguintes tópicos:

  • Como configurar e mapear declarações usando um cliente do OpenID Connect
  • Definir o nome e a declaração de função
  • Redefinir os namespaces de declarações
  • Personalizar, estender as declarações por meio de TransformAsync

Como mapear declarações usando a autenticação do OpenID Connect

As declarações de perfil podem ser retornadas no id_token, que é retornado após uma autenticação bem-sucedida. O aplicativo cliente do ASP.NET Core requer apenas o escopo do perfil. Ao usar o id_token para declarações, nenhum mapeamento de declarações extra é necessário.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
   });

Outra maneira de obter as declarações do usuário é usar a API de Informações do Usuário do OpenID Connect. O aplicativo cliente do ASP.NET Core usa a propriedade GetClaimsFromUserInfoEndpoint para configurar isso. Uma diferença importante em relação às primeiras configurações é que você deve especificar as declarações necessárias por meio do método MapUniqueJsonKey, caso contrário, somente as declarações padrão name, given_name e email estarão disponíveis no aplicativo cliente. As declarações incluídas no id_token são mapeadas por padrão. Essa é a principal diferença para a primeira opção. Você deve definir explicitamente algumas das declarações necessárias.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       options.SignInScheme = "Cookies";
       options.Authority = "-your-identity-provider-";
       options.RequireHttpsMetadata = true;
       options.ClientId = "-your-clientid-";
       options.ClientSecret = "-your-client-secret-from-user-secrets-or-keyvault";
       options.ResponseType = "code";
       options.UsePkce = true;
       options.Scope.Add("profile");
       options.SaveTokens = true;
       options.GetClaimsFromUserInfoEndpoint = true;
       options.ClaimActions.MapUniqueJsonKey("preferred_username", "preferred_username");
       options.ClaimActions.MapUniqueJsonKey("gender", "gender");
   }); 

Mapeamento de declaração de nome e declaração de função

A declaração Nome e a declaração Função são mapeadas para propriedades padrão no contexto HTTP ASP.NET Core. Às vezes, é necessário usar declarações diferentes para as propriedades padrão ou a declaração de nome e a declaração de função não correspondem aos valores padrão. As declarações podem ser mapeadas usando a propriedade TokenValidationParameters e definidas como qualquer declaração conforme necessário. Os valores das declarações podem ser usados diretamente na propriedade User.Identity.Name e nas funções.

Se o User.Identity.Name não tiver nenhum valor ou as funções estiverem ausentes, verifique os valores nas declarações retornadas e defina os valores NameClaimType e RoleClaimType. As declarações retornadas da autenticação do cliente podem ser exibidas no contexto HTTP.

public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
   .AddCookie()
   .AddOpenIdConnect(options =>
   {
       // other options...
       options.TokenValidationParameters = new TokenValidationParameters
       {
         NameClaimType = "email", 
         // RoleClaimType = "role"
       };
   });

Namespaces de declarações, namespaces padrão

O ASP.NET Core adiciona namespaces padrão a algumas declarações conhecidas, o que pode não ser necessário no aplicativo. Opcionalmente, desabilite esses namespaces adicionados e use as declarações exatas que o servidor OpenID Connect criou.

public void Configure(IApplicationBuilder app)
{
    JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();

Estender ou adicionar declarações personalizadas por meio de IClaimsTransformation

A interface IClaimsTransformation pode ser usada para adicionar declarações extras à classe ClaimsPrincipal. A interface exige um único método TransformAsync. Esse método pode ser chamado várias vezes. Só adicione uma nova declaração se ela ainda não existir no ClaimsPrincipal. Um ClaimsIdentity é criado para adicionar as novas declarações e isso pode ser adicionado ao ClaimsPrincipal.

public class MyClaimsTransformation : IClaimsTransformation
{
    public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
    {
       ClaimsIdentity claimsIdentity = new ClaimsIdentity();
       var claimType = "myNewClaim";
       if (!principal.HasClaim(claim => claim.Type == claimType))
       {		   
          claimsIdentity.AddClaim(new Claim(claimType, "myClaimValue"));
       }

       principal.AddIdentity(claimsIdentity);
       return Task.FromResult(principal);
    }
}

A interface IClaimsTransformation e a classe MyClaimsTransformation podem ser adicionadas no método ConfigureServices como um serviço.

public void ConfigureServices(IServiceCollection services)
{
    services.AddTransient<IClaimsTransformation, MyClaimsTransformation>();

Como estender ou adicionar declarações personalizadas no ASP.NET Core Identity

Consulte a seguinte documentação:

Adicionar declarações a Identity por meio de IUserClaimsPrincipalFactory

Mapear declarações de provedores de identity externos

Consulte a seguinte documentação:

Para saber mais, consulte Persistir declarações e tokens adicionais de provedores externos no ASP.NET Core.