Migración de la autenticación y Identity a ASP.NET Core 2.0

Por Scott Addie y Hao Kung

ASP.NET Core 2.0 tiene un nuevo modelo para la autenticación y Identity que simplifica la configuración mediante servicios. Las aplicaciones de ASP.NET Core 1.x que usen la autenticación o Identity se pueden actualizar para usar el nuevo modelo, tal y como se describe a continuación.

Actualizar espacios de nombres

Se encontraron clases en 1.x, como IdentityRole y IdentityUser, en el espacio de nombres Microsoft.AspNetCore.Identity.EntityFrameworkCore.

En la versión 2.0, el espacio de nombres Microsoft.AspNetCore.Identity se convirtió en el nuevo hogar de varias de estas clases. Con el código predeterminado Identity, las clases afectadas incluyen ApplicationUser y Startup. Ajuste las instrucciones using para resolver las referencias afectadas.

Servicios y middleware de autenticación

En los proyectos de 1.x, la autenticación se configura mediante middleware. Se invoca un método de middleware para cada esquema de autenticación que desee admitir.

En el ejemplo de 1.x siguiente se configura la autenticación de Facebook con Identity en 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"]
    });
}

En los proyectos de 2.0, la autenticación se configura a través de servicios. Cada esquema de autenticación se registra en el método ConfigureServices de Startup.cs. El método UseIdentity se reemplaza por UseAuthentication.

En el ejemplo de 2.0 siguiente se configura la autenticación de Facebook con Identity en 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();
}

El método UseAuthentication agrega un único componente de middleware de autenticación, que es responsable de la autenticación automática y del control de solicitudes de autenticación remota. Reemplaza todos los componentes de middleware individuales por un único componente de middleware común.

A continuación, se muestran las instrucciones de migración de 2.0 para cada esquema de autenticación principal.

Seleccione una de las dos opciones siguientes y realice los cambios necesarios en Startup.cs:

  1. Use cookie con Identity

    • Reemplace UseIdentity por UseAuthentication en el método Configure:

      app.UseAuthentication();
      
    • Invoque el método AddIdentity en el método ConfigureServices para agregar los servicios de autenticación cookie.

    • Opcionalmente, invoque el método ConfigureApplicationCookie o ConfigureExternalCookie en el método ConfigureServices para ajustar la configuración de Identitycookie.

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

    • Reemplace la llamada de método UseCookieAuthentication en el método Configure por UseAuthentication:

      app.UseAuthentication();
      
    • Invoque los métodos AddAuthentication y AddCookie en el 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";
              });
      

Autenticación de portador JWT

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseJwtBearerAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddJwtBearer en el método ConfigureServices:

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

    Este fragmento de código no usa Identity, por lo que el esquema predeterminado se debería establecer pasando JwtBearerDefaults.AuthenticationScheme al método AddAuthentication.

Autenticación de OpenID Connect (OIDC)

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseOpenIdConnectAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddOpenIdConnect en el 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"];
    });
    
  • Reemplace la propiedad PostLogoutRedirectUri de la acción OpenIdConnectOptions por SignedOutRedirectUri:

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

Autenticación con Facebook

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseFacebookAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddFacebook en el método ConfigureServices:

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

Autenticación con Google

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseGoogleAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddGoogle en el método ConfigureServices:

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

Autenticación con cuenta Microsoft

Para obtener más información sobre la autenticación de cuentas de Microsoft, consulte esta incidencia de GitHub.

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseMicrosoftAccountAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddMicrosoftAccount en el método ConfigureServices:

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

Autenticación con Twitter

Realice los siguientes cambios en Startup.cs.

  • Reemplace la llamada de método UseTwitterAuthentication en el método Configure por UseAuthentication:

    app.UseAuthentication();
    
  • Invoque el método AddTwitter en el método ConfigureServices:

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

Establecimiento de esquemas de autenticación predeterminados

En la 1.x, las propiedades AutomaticAuthenticate y AutomaticChallenge de la clase base AuthenticationOptions estaban diseñadas para establecerse en un único esquema de autenticación. No había buena manera de aplicar esto.

En la 2.0, estas dos propiedades se han quitado como propiedades en la instancia individual AuthenticationOptions. Se pueden configurar en la llamada al método AddAuthentication dentro del método ConfigureServices de Startup.cs:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

En el fragmento de código anterior, el esquema predeterminado se establece en CookieAuthenticationDefaults.AuthenticationScheme ("Cookie").

Como alternativa, use una versión sobrecargada del método AddAuthentication para establecer más de una propiedad. En el siguiente ejemplo de método sobrecargado, el esquema predeterminado se establece en CookieAuthenticationDefaults.AuthenticationScheme. El esquema de autenticación se puede especificar alternativamente dentro de los atributos [Authorize] individuales o las directivas de autorización.

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

Defina un esquema predeterminado en 2.0 si se cumpliera una de las condiciones siguientes:

  • Desea que al usuario se le inicie sesión automáticamente
  • Use las directivas de atributo o autorización [Authorize] sin especificar esquemas

El método AddIdentity es una excepción a esta regla. Este método agrega cookie por usted y establece los esquemas de autenticación y desafío predeterminados en la aplicación cookieIdentityConstants.ApplicationScheme. Además, establece el esquema de inicio de sesión predeterminado en el externo cookieIdentityConstants.ExternalScheme.

Uso de extensiones de autenticación HttpContext

La interfaz IAuthenticationManager es el punto de entrada principal en el sistema de autenticación 1.x. Se ha reemplazado por un nuevo conjunto de métodos de extensión HttpContext en el espacio de nombres Microsoft.AspNetCore.Authentication.

Por ejemplo, los proyectos de 1.x hacen referencia a una propiedad Authentication:

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

En los proyectos de 2.0, importe el espacio de nombres Microsoft.AspNetCore.Authentication y elimine las referencias de propiedad Authentication:

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

Autenticación de Windows (HTTP.sys / IISIntegration)

Hay dos variaciones de autenticación de Windows:

  • El host solo permite usuarios autenticados. Esta variación no se ve afectada por los cambios de 2.0.

  • El host permite tanto usuarios anónimos como autenticados. Esta variación se ve afectada por los cambios de 2.0. Por ejemplo, la aplicación debería permitir usuarios anónimos en las capas iiS o HTTP.sys, pero autorizar a los usuarios en el nivel de controlador. En este escenario, establezca el esquema predeterminado en el método Startup.ConfigureServices.

    Para Microsoft.AspNetCore.Server.IISIntegration, establezca el esquema predeterminado en IISDefaults.AuthenticationScheme:

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

    Para Microsoft.AspNetCore.Server.HttpSys, establezca el esquema predeterminado en HttpSysDefaults.AuthenticationScheme:

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

    Si se produjera un error al establecer el esquema predeterminado, se impedirá que la solicitud de autorización (desafío) funcione con la siguiente excepción:

    System.InvalidOperationException: no se ha especificado authenticationScheme y no se ha encontrado DefaultChallengeScheme.

Para más información, vea Configuración de la autenticación de Windows en ASP.NET Core.

IdentityCookieInstancias de opciones

Un efecto secundario de los cambios de 2.0 es el cambio al uso de opciones con nombre en lugar de instancias de opciones cookie. Se quita la capacidad de personalizar los nombres de esquema Identitycookie.

Por ejemplo, los proyectos de 1.x usan la inyección de constructores para pasar un parámetro IdentityCookieOptions a AccountController.cs y ManageController.cs. Se accede al esquema de autenticación cookie externa desde la instancia proporcionada:

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>();
}

La inyección de constructores mencionada anteriormente se convierte en innecesaria en proyectos de 2.0 y el campo _externalCookieScheme se puede eliminar:

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>();
}

Los proyectos de 1.x usaron el campo _externalCookieScheme de la siguiente manera:

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

En los proyectos de 2.0, reemplace el código anterior por lo siguiente. La constante IdentityConstants.ExternalScheme se puede usar directamente.

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

Resuelva la llamada SignOutAsync recién agregada importando el siguiente espacio de nombres:

using Microsoft.AspNetCore.Authentication;

Agregue IdentityPropiedades de navegación POCO de usuario

Se han quitado las propiedades de navegación de Entity Framework (EF) Core del objeto POCO IdentityUser base (objeto CLR antiguo sin formato). Si el proyecto de 1.x usó estas propiedades, agréguelas manualmente al proyecto de 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 claves externas duplicadas al ejecutar Migraciones EF Core, agregue lo siguiente al método OnModelCreating de la clase IdentityDbContext (después de la llamada 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);
}

Replace GetExternalAuthenticationSchemes

El método GetExternalAuthenticationSchemes sincrónico se quitó en favor de una versión asincrónica. Los proyectos de 1.x tienen el código siguiente en Controllers/ManageController.cs:

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

Este método también aparece en 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>
    }
}

En los proyectos de 2.0, use el método GetExternalAuthenticationSchemesAsync. El cambio en ManageController.cs es similar al código siguiente:

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

En Login.cshtml, la propiedad AuthenticationScheme a la que se tiene acceso en el bucle foreach cambia a 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>
    }
}

Cambio de propiedad ManageLoginsViewModel

Un objeto ManageLoginsViewModel se usa en la acción ManageLogins de ManageController.cs. En los proyectos de 1.x, el tipo de valor devuelto de la propiedad OtherLogins del objeto es IList<AuthenticationDescription>. Este tipo de valor devuelto requiere una importación 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; }
    }
}

En los proyectos de 2.0, el tipo de valor devuelto cambia a IList<AuthenticationScheme>. Este nuevo tipo de valor devuelto requiere reemplazar la importación Microsoft.AspNetCore.Http.Authentication por una importación 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 adicionales

Para obtener más información, consulte la incidencia Discusión sobre Auth 2.0 en GitHub.