Migrar a autenticação e a Identity para o ASP.NET Core 2.0

Por Scott Addie e Hao Kung

O ASP.NET Core 2.0 tem um novo modelo para autenticação e Identity isso simplifica a configuração usando serviços. Os aplicativos de ASP.NET Core 1.x que usam autenticação ou Identity podem ser atualizados para usar o novo modelo, conforme descrito abaixo.

Atualizar namespaces

No 1.x, classes como IdentityRole e IdentityUser foram encontradas no namespace Microsoft.AspNetCore.Identity.EntityFrameworkCore.

Na versão 2.0, o namespace Microsoft.AspNetCore.Identity tornou-se a nova casa para várias dessas classes. Com o código padrão Identity, as classes afetadas incluem ApplicationUser e Startup. Ajuste suas instruções using para resolver as referências afetadas.

Middleware e serviços de autenticação

Em projetos do 1.x, a autenticação é configurada por meio do middleware. Um método middleware é invocado para cada esquema de autenticação que você deseja dar suporte.

O exemplo do 1.x a seguir configura a autenticação do Facebook com Identity em Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory)
{
    app.UseIdentity();
    app.UseFacebookAuthentication(new FacebookOptions {
        AppId = Configuration["auth:facebook:appid"],
        AppSecret = Configuration["auth:facebook:appsecret"]
    });
}

Em projetos do 2.0, a autenticação é configurada por meio de serviços. Cada esquema de autenticação é registrado no método ConfigureServices de Startup.cs. O método UseIdentity é substituído por UseAuthentication.

O exemplo do 2.0.x a seguir configura a autenticação do Facebook com Identity em Startup.cs:

public void ConfigureServices(IServiceCollection services)
{
    services.AddIdentity<ApplicationUser, IdentityRole>()
            .AddEntityFrameworkStores<ApplicationDbContext>();

    // If you want to tweak Identity cookies, they're no longer part of IdentityOptions.
    services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
}

public void Configure(IApplicationBuilder app, ILoggerFactory loggerfactory) {
    app.UseAuthentication();
}

O método UseAuthentication adiciona um único componente de middleware de autenticação, que é responsável pela autenticação automática e pelo tratamento de solicitações de autenticação remota. Ele substitui todos os componentes de middleware individuais por um único componente de middleware comum.

As instruções de migração do 2.0 para cada esquema de autenticação principal estão descritas abaixo.

Selecione uma das duas opções abaixo e faça as alterações necessárias em Startup.cs:

  1. Use cookies com Identity

    • Substitua UseIdentity por UseAuthentication no método Configure:

      app.UseAuthentication();
      
    • Invoque o método AddIdentity no método ConfigureServices para adicionar os serviços de autenticação cookie.

    • Opcionalmente, invoque o método ConfigureApplicationCookie ou ConfigureExternalCookie no método ConfigureServices para ajustar as configurações do Identitycookie.

      services.AddIdentity<ApplicationUser, IdentityRole>()
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultTokenProviders();
      
      services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
      
  2. Use cookies sem Identity

    • Substitua a chamada de método UseCookieAuthentication no método Configure por UseAuthentication:

      app.UseAuthentication();
      
    • Invoque os métodos AddAuthentication e AddCookie no método ConfigureServices:

      // If you don't want the cookie to be automatically authenticated and assigned to HttpContext.User,
      // remove the CookieAuthenticationDefaults.AuthenticationScheme parameter passed to AddAuthentication.
      services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
              .AddCookie(options =>
              {
                  options.LoginPath = "/Account/LogIn";
                  options.LogoutPath = "/Account/LogOff";
              });
      

Autenticação do Portador JWT

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseJwtBearerAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddJwtBearer no método ConfigureServices:

    services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
            .AddJwtBearer(options =>
            {
                options.Audience = "http://localhost:5001/";
                options.Authority = "http://localhost:5000/";
            });
    

    Esse snippet de código não usa Identity, portanto, o esquema padrão deve ser definido passando JwtBearerDefaults.AuthenticationScheme para o método AddAuthentication.

Falha na autenticação OpenID Connect (OIDC)

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseOpenIdConnectAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddOpenIdConnect no método ConfigureServices:

    services.AddAuthentication(options =>
    {
        options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddOpenIdConnect(options =>
    {
        options.Authority = Configuration["auth:oidc:authority"];
        options.ClientId = Configuration["auth:oidc:clientid"];
    });
    
  • Substitua a propriedade PostLogoutRedirectUri na ação OpenIdConnectOptions por SignedOutRedirectUri:

    .AddOpenIdConnect(options =>
    {
        options.SignedOutRedirectUri = "https://contoso.com";
    });
    

Autenticação do Facebook

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseFacebookAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddFacebook no método ConfigureServices:

    services.AddAuthentication()
            .AddFacebook(options =>
            {
                options.AppId = Configuration["auth:facebook:appid"];
                options.AppSecret = Configuration["auth:facebook:appsecret"];
            });
    

Autenticação do Google

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseGoogleAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddGoogle no método ConfigureServices:

    services.AddAuthentication()
            .AddGoogle(options =>
            {
                options.ClientId = Configuration["auth:google:clientid"];
                options.ClientSecret = Configuration["auth:google:clientsecret"];
            });
    

Autenticação de Conta da Microsoft

Para obter mais informações sobre a autenticação da conta Microsoft, consulte esse problema do GitHub.

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseMicrosoftAccountAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddMicrosoftAccount no método ConfigureServices:

    services.AddAuthentication()
            .AddMicrosoftAccount(options =>
            {
                options.ClientId = Configuration["auth:microsoft:clientid"];
                options.ClientSecret = Configuration["auth:microsoft:clientsecret"];
            });
    

Autenticação do Twitter

Faça as seguintes alterações em Startup.cs:

  • Substitua a chamada de método UseTwitterAuthentication no método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque o método AddTwitter no método ConfigureServices:

    services.AddAuthentication()
            .AddTwitter(options =>
            {
                options.ConsumerKey = Configuration["auth:twitter:consumerkey"];
                options.ConsumerSecret = Configuration["auth:twitter:consumersecret"];
            });
    

Como configurar esquemas de autenticação padrão

No 1.x, as propriedades AutomaticAuthenticate e AutomaticChallenge da classe base AuthenticationOptions foram destinadas a ser definidas em um único esquema de autenticação. Não havia uma maneira boa de impor isso.

Na versão 2.0, essas duas propriedades foram removidas como propriedades na instância individual AuthenticationOptions . Elas podem ser configuradas na chamada de método AddAuthentication dentro do método ConfigureServices de Startup.cs:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

No snippet de código anterior, o esquema padrão é definido como CookieAuthenticationDefaults.AuthenticationScheme ("Cookies").

Como alternativa, use uma versão sobrecarregada do método AddAuthentication para definir mais de uma propriedade. No exemplo de método sobrecarregado a seguir, o esquema padrão é definido como CookieAuthenticationDefaults.AuthenticationScheme. Como alternativa, o esquema de autenticação pode ser especificado em seus atributos individuais ou políticas de autorização do [Authorize].

services.AddAuthentication(options =>
{
    options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});

Defina um esquema padrão em 2.0 se uma das seguintes condições for verdadeira:

  • Você deseja que o usuário seja conectado automaticamente
  • Você usa o atributo ou as políticas de autorização do [Authorize] sem especificar esquemas

A exceção a essa regra é o método AddIdentity. Esse método adiciona cookies para você e define os esquemas de autenticação e de desafio padrão para o aplicativo cookieIdentityConstants.ApplicationScheme. Além disso, ele define o esquema de entrada padrão para o cookieIdentityConstants.ExternalScheme externo.

Usar extensões de autenticação HttpContext

A interface IAuthenticationManager é o principal ponto de entrada no sistema de autenticação 1.x. Ele foi substituído por um novo conjunto de métodos de extensão HttpContext no namespace Microsoft.AspNetCore.Authentication.

Por exemplo, projetos 1.x fazem referência a uma propriedadeAuthentication:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

Em projetos 2.0, importe o namespace Microsoft.AspNetCore.Authentication e exclua as referências de propriedade Authentication:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Autenticação do Windows (HTTP.sys/IISIntegration)

Há duas variações de autenticação do Windows:

  • O host só permite usuários autenticados. Essa variação não é afetada pelas alterações 2.0.

  • O host permite usuários anônimos e autenticados. Essa variação é afetada pelas alterações 2.0. Por exemplo, o aplicativo deve permitir usuários anônimos na camada IIS ou HTTP.sys, mas autorizar usuários no nível do controlador. Nesse cenário, defina o esquema padrão no método Startup.ConfigureServices.

    Para Microsoft.AspNetCore.Server.IISIntegration, defina o esquema padrão como IISDefaults.AuthenticationScheme:

    using Microsoft.AspNetCore.Server.IISIntegration;
    
    services.AddAuthentication(IISDefaults.AuthenticationScheme);
    

    Para Microsoft.AspNetCore.Server.IISIntegration, defina o esquema padrão como HttpSysDefaults.AuthenticationScheme:

    using Microsoft.AspNetCore.Server.HttpSys;
    
    services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
    

    A falha ao definir o esquema padrão impede que a solicitação de autorização (desafio) funcione com a seguinte exceção:

    System.InvalidOperationException: nenhum authenticationScheme foi especificado e não foi encontrado nenhum DefaultAuthenticateScheme.

Para obter mais informações, confira Configurar autenticação do Windows no ASP.NET Core.

Instâncias de opções do IdentityCookie

Um efeito colateral das alterações 2.0 é a opção para usar opções nomeadas em vez de instâncias de opções do cookie. A capacidade de personalizar os nomes do esquema Identitycookie é removida.

Por exemplo, projetos 1.x usam injeção de construtor para passar um parâmetro IdentityCookieOptions para AccountController.cs e ManageController.cs. O esquema de autenticação externa cookie é acessado a partir da instância fornecida:

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IOptions<IdentityCookieOptions> identityCookieOptions,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _externalCookieScheme = identityCookieOptions.Value.ExternalCookieAuthenticationScheme;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

A injeção de construtor mencionada anteriormente torna-se desnecessária em projetos 2.0 e o campo _externalCookieScheme pode ser excluído:

public AccountController(
    UserManager<ApplicationUser> userManager,
    SignInManager<ApplicationUser> signInManager,
    IEmailSender emailSender,
    ISmsSender smsSender,
    ILoggerFactory loggerFactory)
{
    _userManager = userManager;
    _signInManager = signInManager;
    _emailSender = emailSender;
    _smsSender = smsSender;
    _logger = loggerFactory.CreateLogger<AccountController>();
}

Os projetos 1.x usaram o campo _externalCookieScheme da seguinte maneira:

// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);

Em projetos 2.0, substitua o código anterior pelo seguinte. A constante IdentityConstants.ExternalScheme pode ser usada diretamente.

// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);

Resolva a chamada recém-adicionada SignOutAsync importando o seguinte namespace:

using Microsoft.AspNetCore.Authentication;

Adicione Identitypropriedades de navegação POCO do usuário

As propriedades de navegação da Entity Framework (EF) Core do POCO base IdentityUser (Objeto CLR Antigo Simples) foram removidas. Se o projeto 1.x usou essas propriedades, adicione-as manualmente de volta ao projeto 2.0:

/// <summary>
/// Navigation property for the roles this user belongs to.
/// </summary>
public virtual ICollection<IdentityUserRole<int>> Roles { get; } = new List<IdentityUserRole<int>>();

/// <summary>
/// Navigation property for the claims this user possesses.
/// </summary>
public virtual ICollection<IdentityUserClaim<int>> Claims { get; } = new List<IdentityUserClaim<int>>();

/// <summary>
/// Navigation property for this users login accounts.
/// </summary>
public virtual ICollection<IdentityUserLogin<int>> Logins { get; } = new List<IdentityUserLogin<int>>();

Para evitar chaves estrangeiras duplicadas ao executar migrações EF Core, adicione o seguinte ao método IdentityDbContext da classe OnModelCreating (após a chamada base.OnModelCreating();):

protected override void OnModelCreating(ModelBuilder builder)
{
    base.OnModelCreating(builder);
    // Customize the ASP.NET Core Identity model and override the defaults if needed.
    // For example, you can rename the ASP.NET Core Identity table names and more.
    // Add your customizations after calling base.OnModelCreating(builder);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Claims)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Logins)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);

    builder.Entity<ApplicationUser>()
        .HasMany(e => e.Roles)
        .WithOne()
        .HasForeignKey(e => e.UserId)
        .IsRequired()
        .OnDelete(DeleteBehavior.Cascade);
}

Substituir GetExternalAuthenticationSchemes

O método síncrono GetExternalAuthenticationSchemes foi removido em favor de uma versão assíncrona. Os projetos 1.x têm o seguinte código em Controllers/ManageController.cs:

var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();

Esse método também aparece em Views/Account/Login.cshtml :

@{
    var loginProviders = SignInManager.GetExternalAuthenticationSchemes().ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.AuthenticationScheme" title="Log in using your @provider.DisplayName account">@provider.AuthenticationScheme</button>
                    }
                </p>
            </div>
        </form>
    }
}

Em projetos 2.0, use o método GetExternalAuthenticationSchemesAsync. A alteração em ManageController.cs é semelhante ao código a seguir:

var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();

No Login.cshtml, a propriedade AuthenticationScheme acessada no loop foreach é alterada para Name:

@{
    var loginProviders = (await SignInManager.GetExternalAuthenticationSchemesAsync()).ToList();
    if (loginProviders.Count == 0)
    {
        <div>
            <p>
                There are no external authentication services configured. See <a href="https://go.microsoft.com/fwlink/?LinkID=532715">this article</a>
                for details on setting up this ASP.NET application to support logging in via external services.
            </p>
        </div>
    }
    else
    {
        <form asp-controller="Account" asp-action="ExternalLogin" asp-route-returnurl="@ViewData["ReturnUrl"]" method="post" class="form-horizontal">
            <div>
                <p>
                    @foreach (var provider in loginProviders)
                    {
                        <button type="submit" class="btn btn-default" name="provider" value="@provider.Name" title="Log in using your @provider.DisplayName account">@provider.DisplayName</button>
                    }
                </p>
            </div>
        </form>
    }
}

Alteração da propriedade ManageLoginsViewModel

Um objeto ManageLoginsViewModel é usado na ação ManageLogins de ManageController.cs. Em projetos 1.x, o tipo de retorno da propriedade OtherLogins do objeto é IList<AuthenticationDescription>. Esse tipo de retorno requer uma importação de Microsoft.AspNetCore.Http.Authentication:

using System.Collections.Generic;
using Microsoft.AspNetCore.Http.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore1App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationDescription> OtherLogins { get; set; }
    }
}

Em projetos 2.0, o tipo de retorno muda para IList<AuthenticationScheme>. Esse novo tipo de retorno requer a substituição da importação Microsoft.AspNetCore.Http.Authentication por uma importação Microsoft.AspNetCore.Authentication.

using System.Collections.Generic;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;

namespace AspNetCoreDotNetCore2App.Models.ManageViewModels
{
    public class ManageLoginsViewModel
    {
        public IList<UserLoginInfo> CurrentLogins { get; set; }

        public IList<AuthenticationScheme> OtherLogins { get; set; }
    }
}

Recursos adicionais

Para obter mais informações, consulte Discussão sobre o problema do Auth 2.0 no GitHub.