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 domicile 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.

Intergiciel et services d’authentification

Dans les projets 1.x, l’authentification est configurée via un intergiciel. Une méthode d’intergiciel 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 intergiciel 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 intergiciel individuel par un composant unique d’un intergiciel commun.

Vous trouverez ci-dessous des instructions liées à la migration 2.0 pour chaque schéma d’authentification majeur.

Sélectionnez l’une des deux options ci-dessous, puis apportez les modifications nécessaires à Startup.cs :

  1. Utiliser des cookies avec Identity

    • Remplacez UseIdentity par UseAuthentication dans la méthode Configure :

      app.UseAuthentication();
      
    • Appelez la méthode AddIdentity dans la méthode ConfigureServices pour ajouter les services d’authentification cookie.

    • Si vous le souhaitez, appelez la méthode ConfigureApplicationCookie ou la méthode ConfigureExternalCookie dans la méthode ConfigureServices pour modifier les paramètres d’Identity du cookie.

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

    • Remplacez l’appel de la méthode UseCookieAuthentication dans la méthode Configure par UseAuthentication :

      app.UseAuthentication();
      
    • Appelez les méthodes AddAuthentication et AddCookie dans la méthode 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";
              });
      

Authentification du porteur JWT

Dans Startup.cs, effectuez les changements suivants :

  • Remplacez l’appel de la méthode UseJwtBearerAuthentication dans la méthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddJwtBearer dans la méthode ConfigureServices :

    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éthode AddAuthentication.

Authentification OpenID Connect (OIDC)

Dans Startup.cs, effectuez les changements suivants :

  • Remplacez l’appel de la méthode UseOpenIdConnectAuthentication dans la méthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddOpenIdConnect dans la méthode 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"];
    });
    
  • Remplacez la propriété PostLogoutRedirectUri dans l’action OpenIdConnectOptions par SignedOutRedirectUri :

    .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éthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddFacebook dans la méthode ConfigureServices :

    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éthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddGoogle dans la méthode ConfigureServices :

    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éthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddMicrosoftAccount dans la méthode ConfigureServices :

    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éthode Configure par UseAuthentication :

    app.UseAuthentication();
    
  • Appelez la méthode AddTwitter dans la méthode ConfigureServices :

    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 d’options IdentityCookie

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 utilisateur Identity

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.