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

Por Scott Addie y Hao Scott

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

Actualización de espacios de nombres

En la versión 1.x, se encontraron clases como IdentityRole y en el espacio de nombres IdentityUser Microsoft.AspNetCore.Identity.EntityFrameworkCore .

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

Middleware y servicios de autenticación

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

En el ejemplo 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 2.0, la autenticación se configura a través de servicios. Cada esquema de autenticación se registra en ConfigureServices el método de Startup.cs. El UseIdentity método se reemplaza por UseAuthentication .

En el ejemplo 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 agrega un único componente de middleware de autenticación, que es responsable de la autenticación UseAuthentication automática y del control de las 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 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. Uso cookie de con Identity

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

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

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

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

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

      app.UseAuthentication();
      
    • AddAuthenticationInvoque AddCookie los métodos y 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 UseJwtBearerAuthentication llamada al método en el método por Configure UseAuthentication :

    app.UseAuthentication();
    
  • AddJwtBearerInvoque el método 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 debe establecerse pasando al JwtBearerDefaults.AuthenticationScheme método AddAuthentication .

Autenticación de Conectar OpenID (OIDC)

Realice los siguientes cambios en Startup.cs:

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

    app.UseAuthentication();
    
  • AddOpenIdConnectInvoque el método 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 PostLogoutRedirectUri propiedad de la acción por OpenIdConnectOptions SignedOutRedirectUri :

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

Autenticación con Facebook

Realice los siguientes cambios en Startup.cs:

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

    app.UseAuthentication();
    
  • AddFacebookInvoque el método 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 UseGoogleAuthentication llamada al método en el método por Configure UseAuthentication :

    app.UseAuthentication();
    
  • AddGoogleInvoque el método 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 Microsoft, consulte este GitHub problema.

Realice los siguientes cambios en Startup.cs:

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

    app.UseAuthentication();
    
  • AddMicrosoftAccountInvoque el método 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 UseTwitterAuthentication llamada al método en el método por Configure UseAuthentication :

    app.UseAuthentication();
    
  • AddTwitterInvoque el método 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 versión 1.x, las propiedades y de la clase base AuthenticationOptions estaban diseñadas para establecerse AutomaticAuthenticate en un esquema de AutomaticChallenge autenticación único. No había ninguna manera correcta de aplicar esto.

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

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

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

Como alternativa, use una versión sobrecargada del AddAuthentication método 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 también se puede especificar 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 cumple una de las condiciones siguientes:

  • Quiere que el usuario inicie sesión automáticamente.
  • Use el atributo [Authorize] o las directivas de autorización sin especificar esquemas.

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

Uso de extensiones de autenticación httpContext

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

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

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

En proyectos de la versión 2.0, importe el espacio Microsoft.AspNetCore.Authentication de nombres y elimine las referencias de Authentication propiedad:

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

Windows Autenticación (HTTP.sys/IISIntegration)

Hay dos variaciones de Windows autenticación:

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

  • El host permite usuarios anónimos y autenticados. Esta variación se ve afectada por los cambios de la versión 2.0. Por ejemplo, la aplicación debe permitir usuarios anónimos en iis oHTTP.sys nivel, pero autorizar a los usuarios en el nivel de controlador. En este escenario, establezca el esquema predeterminado en el Startup.ConfigureServices método .

    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 no se establece el esquema predeterminado, se impide que la solicitud de autorización (desafío) funcione con la siguiente excepción:

    System.InvalidOperationException: no se especificó authenticationScheme y no se encontró DefaultChallengeScheme.

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

IdentityCookieInstancias de opciones

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

Por ejemplo, los proyectos 1.x usan la inserción de constructores para pasar un parámetro a IdentityCookieOptions AccountController.cs y ManageController.cs. Se accede cookie al esquema de autenticación externo 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 inserción de constructores mencionada anteriormente no es necesaria en los proyectos de 2.0 y _externalCookieScheme el campo 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 1.x usaban _externalCookieScheme el campo de la manera siguiente:

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

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

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

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

using Microsoft.AspNetCore.Authentication;

Agregar propiedades Identity de navegación POCO de usuario

Se han Entity Framework propiedades de navegación básicas (EF) de la base POCO (objeto CLR antiguo IdentityUser sin formato). Si el proyecto 1.x usa estas propiedades, vuelva a agregarlas manualmente al proyecto 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 de la IdentityDbContext OnModelCreating clase (después de la base.OnModelCreating(); llamada):

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

Reemplazar GetExternalAuthenticationSchemes

El método GetExternalAuthenticationSchemes sincrónico se quitó en favor de una versión asincrónica. Los proyectos 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 proyectos de la versión 2.0, use el GetExternalAuthenticationSchemesAsync método . 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 a la que se tiene acceso AuthenticationScheme en el bucle cambia a foreach 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 la propiedad ManageLoginsViewModel

Un ManageLoginsViewModel objeto se usa en la acción de ManageLogins ManageController.cs. En proyectos 1.x, el tipo de valor devuelto de OtherLogins propiedad 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 la versión 2.0, el tipo de valor devuelto cambia a IList<AuthenticationScheme> . Este nuevo tipo de valor devuelto requiere reemplazar Microsoft.AspNetCore.Http.Authentication la importación por una Microsoft.AspNetCore.Authentication importación.

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 explicación del problema de autenticación 2.0 en GitHub.