Migrer une authentification et Identity vers ASP.NET Core 2.0
Par Scott Addie et Hao Kung
ASP.NET Core 2.0 dispose d’un nouveau modèle d’authentification et d’Identity qui simplifie la configuration à l’aide de services. Les applications ASP.NET Core 1.x qui utilisent l’authentification ou Identity peuvent être mises à jour pour utiliser le nouveau modèle, comme indiqué ci-dessous.
Mettre à jour les espaces de noms
Dans la version 1.x, des classes telles que IdentityRole
et IdentityUser
ont été trouvées dans l’espace de noms Microsoft.AspNetCore.Identity.EntityFrameworkCore
.
Dans la version 2.0, l’espace de noms Microsoft.AspNetCore.Identity est devenu le nouveau home de plusieurs de ces classes. Avec le code Identity par défaut, les classes affectées incluent ApplicationUser
et Startup
. Ajustez vos instructions using
pour résoudre les références affectées.
Middleware et services d’authentification
Dans les projets 1.x, l’authentification est configurée via un middleware. Une méthode de middleware est appelée pour chaque schéma d’authentification que vous souhaitez prendre en charge.
L’exemple 1.x suivant configure l’authentification Facebook avec Identity dans 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"]
});
}
Dans les projets 2.0, l’authentification est configurée via des services. Chaque schéma d’authentification est inscrit dans la méthode ConfigureServices
de Startup.cs
. La méthode UseIdentity
est remplacée par UseAuthentication
.
L’exemple 2.0 suivant configure l’authentification Facebook avec Identity dans 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();
}
La méthode UseAuthentication
ajoute un composant middleware d’authentification unique, qui est responsable de l’authentification automatique et de la gestion des requêtes d’authentification à distance. Il remplace tous les composants d’un middleware individuel par un composant unique d’un middleware commun.
Vous trouverez ci-dessous des instructions liées à la migration 2.0 pour chaque schéma d’authentification majeur.
Authentification basée sur un Cookie
Sélectionnez l’une des deux options ci-dessous, puis apportez les modifications nécessaires à Startup.cs
:
Utiliser des cookies avec Identity
Remplacez
UseIdentity
parUseAuthentication
dans la méthodeConfigure
:app.UseAuthentication();
Appelez la méthode
AddIdentity
dans la méthodeConfigureServices
pour ajouter les services d’authentification cookie.Si vous le souhaitez, appelez la méthode
ConfigureApplicationCookie
ou la méthodeConfigureExternalCookie
dans la méthodeConfigureServices
pour modifier les paramètres d’Identity du cookie.services.AddIdentity<ApplicationUser, IdentityRole>() .AddEntityFrameworkStores<ApplicationDbContext>() .AddDefaultTokenProviders(); services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
Utiliser des cookies sans Identity
Remplacez l’appel de la méthode
UseCookieAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez les méthodes
AddAuthentication
etAddCookie
dans la méthodeConfigureServices
:// 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"; });
Authentification du porteur JWT
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseJwtBearerAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddJwtBearer
dans la méthodeConfigureServices
:services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Audience = "http://localhost:5001/"; options.Authority = "http://localhost:5000/"; });
Cet extrait de code n’utilise pas Identity, donc le schéma par défaut doit être défini en passant
JwtBearerDefaults.AuthenticationScheme
à la méthodeAddAuthentication
.
Authentification OpenID Connect (OIDC)
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseOpenIdConnectAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddOpenIdConnect
dans la méthodeConfigureServices
: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"]; });
Remplacez la propriété
PostLogoutRedirectUri
dans l’actionOpenIdConnectOptions
parSignedOutRedirectUri
:.AddOpenIdConnect(options => { options.SignedOutRedirectUri = "https://contoso.com"; });
Authentification Facebook
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseFacebookAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddFacebook
dans la méthodeConfigureServices
:services.AddAuthentication() .AddFacebook(options => { options.AppId = Configuration["auth:facebook:appid"]; options.AppSecret = Configuration["auth:facebook:appsecret"]; });
Authentification Google
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseGoogleAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddGoogle
dans la méthodeConfigureServices
:services.AddAuthentication() .AddGoogle(options => { options.ClientId = Configuration["auth:google:clientid"]; options.ClientSecret = Configuration["auth:google:clientsecret"]; });
Authentification du compte Microsoft
Pour plus d’informations sur l’authentification d’un compte Microsoft, consultez ce problème GitHub.
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseMicrosoftAccountAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddMicrosoftAccount
dans la méthodeConfigureServices
:services.AddAuthentication() .AddMicrosoftAccount(options => { options.ClientId = Configuration["auth:microsoft:clientid"]; options.ClientSecret = Configuration["auth:microsoft:clientsecret"]; });
Authentification Twitter
Dans Startup.cs
, effectuez les changements suivants :
Remplacez l’appel de la méthode
UseTwitterAuthentication
dans la méthodeConfigure
parUseAuthentication
:app.UseAuthentication();
Appelez la méthode
AddTwitter
dans la méthodeConfigureServices
:services.AddAuthentication() .AddTwitter(options => { options.ConsumerKey = Configuration["auth:twitter:consumerkey"]; options.ConsumerSecret = Configuration["auth:twitter:consumersecret"]; });
Paramétrage des schémas d’authentification par défaut
Dans la version 1.x, les propriétés AutomaticAuthenticate
et AutomaticChallenge
de la classe de base AuthenticationOptions étaient destinées à être définies sur un schéma d’authentification unique. Il n’y avait pas de bon moyen de l’appliquer.
Dans la version 2.0, ces deux propriétés ont été supprimées en tant que propriétés sur l’instance individuelle AuthenticationOptions
. Elles peuvent être configurées dans l’appel de la méthode AddAuthentication
de la méthode ConfigureServices
de Startup.cs
:
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
Dans l’extrait de code précédent, le schéma par défaut est défini sur CookieAuthenticationDefaults.AuthenticationScheme
(« Cookies »).
Vous pouvez également utiliser une version surchargée de la méthode AddAuthentication
pour définir plusieurs propriétés. Dans l’exemple de méthode surchargée suivant, le schéma par défaut est défini sur CookieAuthenticationDefaults.AuthenticationScheme
. Le schéma d’authentification peut également être spécifié dans vos attributs individuels [Authorize]
ou vos stratégies d’autorisation.
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
});
Définissez un schéma par défaut dans la version 2.0 si vous répondez à l’une des conditions suivantes :
- Vous souhaitez que l’utilisateur soit connecté automatiquement
- Vous utilisez l’attribut
[Authorize]
ou des stratégies d’autorisation sans spécifier de schémas
La méthode AddIdentity
constitue une exception à cette règle. Cette méthode ajoute des cookies pour vous et définit les schémas d’authentification et de contestation par défaut sur le cookie d’application IdentityConstants.ApplicationScheme
. En outre, il définit le schéma de connexion par défaut sur le cookie externe IdentityConstants.ExternalScheme
.
Utiliser des extensions d’authentification HttpContext
L’interface IAuthenticationManager
est le point d’entrée principal dans le système d’authentification 1.x. Il a été remplacé par un nouvel ensemble de méthodes d’extension HttpContext
dans l’espace de noms Microsoft.AspNetCore.Authentication
.
Par exemple, les projets 1.x référencent une propriété Authentication
:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
Dans les projets 2.0, importez l’espace de noms Microsoft.AspNetCore.Authentication
et supprimez les références de propriété Authentication
:
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
Authentification Windows (HTTP.sys/IISIntegration)
Il existe deux variantes d’authentification Windows :
L’hôte autorise uniquement les utilisateurs authentifiés. Cette variante n’est pas affectée par les modifications de la version 2.0.
L’hôte autorise à la fois les utilisateurs anonymes et les utilisateurs authentifiés. Cette variante est affectée par les modifications de la version 2.0. Par exemple, l’application doit autoriser des utilisateurs anonymes au niveau de la couche IIS ou HTTP.sys, mais autoriser les utilisateurs au niveau du contrôleur. Dans ce scénario, définissez le schéma par défaut dans la méthode
Startup.ConfigureServices
.Pour Microsoft.AspNetCore.Server.IISIntegration, définissez le schéma par défaut sur
IISDefaults.AuthenticationScheme
:using Microsoft.AspNetCore.Server.IISIntegration; services.AddAuthentication(IISDefaults.AuthenticationScheme);
Pour Microsoft.AspNetCore.Server.HttpSys, définissez le schéma par défaut sur
HttpSysDefaults.AuthenticationScheme
:using Microsoft.AspNetCore.Server.HttpSys; services.AddAuthentication(HttpSysDefaults.AuthenticationScheme);
L’impossibilité de définir le schéma par défaut empêche la requête d’autorisation (de contestation) de fonctionner et présente l’exception suivante :
System.InvalidOperationException
: Aucun authenticationScheme n’a été spécifié et aucun DefaultChallengeScheme n’a été trouvé.
Pour plus d’informations, consultez Configurer l’authentification Windows par certificat dans ASP.NET Core.
Instances IdentityCookieOptions
L’un des effets secondaires des modifications de la version 2.0 est le passage à une utilisation d’options nommées plutôt que d’instances d’options cookie. La possibilité de personnaliser les noms de schéma Identity d’un cookie est supprimée.
Par exemple, les projets 1.x utilisent l’injection de constructeur pour passer un paramètre IdentityCookieOptions
dans AccountController.cs
et ManageController.cs
. Le schéma d’authentification externe cookie est accessible à partir de la instance fournie :
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>();
}
L’injection de constructeur mentionnée ci-dessus devient inutile dans les projets 2.0, et le champ _externalCookieScheme
peut être supprimé :
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>();
}
Les projets 1.x ont utilisé le champ _externalCookieScheme
comme suit :
// Clear the existing external cookie to ensure a clean login process
await HttpContext.Authentication.SignOutAsync(_externalCookieScheme);
Dans les projets 2.0, remplacez le code précédent par le code suivant. La constante IdentityConstants.ExternalScheme
peut être utilisée directement.
// Clear the existing external cookie to ensure a clean login process
await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);
Résolvez l’appel SignOutAsync
qui vient d’être ajouté en important l’espace de noms suivant :
using Microsoft.AspNetCore.Authentication;
Ajouter les propriétés de navigation POCO d’un IdentityUser
Les propriétés de navigation Core d’Entity Framework (EF) d’un POCO (Plain Old CLR Object) de base IdentityUser
ont été supprimées. Si votre projet 1.x utilisait ces propriétés, rajoutez-les manuellement au projet 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>>();
Pour éviter les clés étrangères en double lors de l’exécution des migrations d’EF Core, ajoutez ce qui suit à la méthode IdentityDbContext
de votre classe OnModelCreating
(après l’appel à 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);
}
Remplacer GetExternalAuthenticationSchemes
La méthode synchrone GetExternalAuthenticationSchemes
a été supprimée au profit d’une version asynchrone. Le code suivant est intégré aux projets 1.x dans Controllers/ManageController.cs
:
var otherLogins = _signInManager.GetExternalAuthenticationSchemes().Where(auth => userLogins.All(ul => auth.AuthenticationScheme != ul.LoginProvider)).ToList();
Cette méthode apparaît également dans 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>
}
}
Dans les projets 2.0, utilisez la méthode GetExternalAuthenticationSchemesAsync. La modification apportée à ManageController.cs
ressemble au code suivant :
var schemes = await _signInManager.GetExternalAuthenticationSchemesAsync();
var otherLogins = schemes.Where(auth => userLogins.All(ul => auth.Name != ul.LoginProvider)).ToList();
Dans Login.cshtml
, la propriété AuthenticationScheme
accessible dans la boucle foreach
devient 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>
}
}
Modification de la propriété ManageLoginsViewModel
Un objet ManageLoginsViewModel
est utilisé dans l’action ManageLogins
de ManageController.cs
. Dans les projets 1.x, le type de retour de la propriété OtherLogins
de l’objet est IList<AuthenticationDescription>
. Ce type de retour nécessite une importation 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; }
}
}
Dans les projets 2.0, le type de retour devient IList<AuthenticationScheme>
. Ce nouveau type de retour nécessite le remplacement de l’importation Microsoft.AspNetCore.Http.Authentication
par une importation 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; }
}
}
Ressources supplémentaires
Pour plus d’informations, consultez la Discussion sur Auth 2.0 de GitHub.