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 home 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.
Autenticación basada en Cookie
Seleccione una de las dos opciones siguientes y realice los cambios necesarios en Startup.cs
:
Uso de cookies con Identity
Reemplace
UseIdentity
porUseAuthentication
en el métodoConfigure
:app.UseAuthentication();
Invoque el método
AddIdentity
en el métodoConfigureServices
para agregar los servicios de autenticación cookie.Opcionalmente, invoque el método
ConfigureApplicationCookie
oConfigureExternalCookie
en el métodoConfigureServices
para ajustar la configuración de Identitycookie.services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
Uso de cookies sin Identity
Reemplace la llamada de método
UseCookieAuthentication
en el métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque los métodos
AddAuthentication
yAddCookie
en el métodoConfigureServices
:// 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étodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddJwtBearer
en el métodoConfigureServices
: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étodoAddAuthentication
.
Autenticación de OpenID Connect (OIDC)
Realice los siguientes cambios en Startup.cs
.
Reemplace la llamada de método
UseOpenIdConnectAuthentication
en el métodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddOpenIdConnect
en el métodoConfigureServices
: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ónOpenIdConnectOptions
porSignedOutRedirectUri
:.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étodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddFacebook
en el métodoConfigureServices
: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étodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddGoogle
en el métodoConfigureServices
: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étodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddMicrosoftAccount
en el métodoConfigureServices
: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étodoConfigure
porUseAuthentication
:app.UseAuthentication();
Invoque el método
AddTwitter
en el métodoConfigureServices
: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
("Cookies").
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 cookies por ti 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.
Instancias de IdentityCookieOptions
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;
Agrega propiedades de navegación IdentityUser POCO
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.