Migrieren von Authentifizierung und Identity zu ASP.NET Core 2.0

Von Scott Addie und Hao Kung

ASP.NET Core 2.0 verfügt über ein neues Modell für die Authentifizierung und Identity, was die Konfiguration mithilfe von Diensten vereinfacht. ASP.NET Core 1.x-Anwendungen, die Authentifizierung verwenden oder Identity verwenden, können wie unten beschrieben aktualisiert werden, um das neue Modell zu verwenden.

Aktualisieren von Namespaces

In Version 1.x waren Klassen wie „IdentityRole“ und „IdentityUser“ im Namespace „Microsoft.AspNetCore.Identity.EntityFrameworkCore“ zu finden.

In Version 2.0 wurde der Namespace „Microsoft.AspNetCore.Identity“ zum neuen Zuhause mehrerer solcher Klassen. Mit dem Standardcode „Identity“ umfassen die betroffenen Klassen „ApplicationUser“ und „Startup“. Passen Sie Ihre „using“-Anweisungen an, um die betroffenen Verweise aufzulösen.

Middleware für Authentifizierung und Dienste

In 1.x-Projekten wird die Authentifizierung mithilfe von Middleware konfiguriert. Für jedes Authentifizierungsschema, das Sie unterstützen möchten, wird eine Middlewaremethode aufgerufen.

Im folgenden 1.x-Beispiel wird die Facebook-Authentifizierung mit „Identity“ in „Startup.cs“ konfiguriert:

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

In 2.0.x-Projekten wird die Authentifizierung mithilfe von Diensten konfiguriert. Jedes Authentifizierungsschema wird in der „ConfigureServices“-Methode von „Startup.cs“ registriert. Die „UseIdentity“-Methode wird durch „UseAuthentication“ ersetzt.

Im folgenden 2.0.x-Beispiel wird die Facebook-Authentifizierung mit „Identity“ in „Startup.cs“ konfiguriert:

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

Die „UseAuthentication“-Methode fügt eine einzelne Authentifizierungsmiddleware-Komponente hinzu, die für die automatische Authentifizierung und die Verarbeitung von Remote-Authentifizierungsanforderungen zuständig ist. Sie ersetzt alle individuellen durch eine einzelne, gemeinsame Middlewarekomponente.

Im Folgenden finden Sie 2.0-Migrationsanweisungen für jedes wichtige Authentifizierungsschema.

Wählen Sie eine der beiden folgenden Optionen aus, und nehmen Sie die erforderlichen Änderungen in „Startup.cs“ vor:

  1. Verwenden von „cookie“ mit „Identity“

    • Ersetzen Sie „UseIdentity“ in der „UseAuthentication“ -Methode durch „Configure“:

      app.UseAuthentication();
      
    • Rufen Sie die „AddIdentity“-Methode in der „ConfigureServices“-Methode auf, um die „cookie“-Authentifizierungsdienste hinzuzufügen.

    • Rufen Sie optional die „ConfigureApplicationCookie“- oder „ConfigureExternalCookie“-Methode in der „ConfigureServices“-Methode auf, um die „Identitycookie“-Einstellungen zu optimieren.

      services.AddIdentity<ApplicationUser, IdentityRole>()
              .AddEntityFrameworkStores<ApplicationDbContext>()
              .AddDefaultTokenProviders();
      
      services.ConfigureApplicationCookie(options => options.LoginPath = "/Account/LogIn");
      
  2. Verwenden von „cookie“ ohne „Identity“

    • Ersetzen Sie den Methodenaufruf „UseCookieAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

      app.UseAuthentication();
      
    • Rufen Sie die „AddAuthentication“- und „AddCookie“-Methode in der „ConfigureServices“-Methode auf:

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

JWT-Bearerauthentifizierung

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseJwtBearerAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddJwtBearer“-Methode in der „ConfigureServices“-Methode auf:

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

    Dieser Codeschnipsel verwendet „Identity“ nicht, sodass das Standardschema durch Übergeben von „JwtBearerDefaults.AuthenticationScheme“ an die „AddAuthentication“-Methode festgelegt werden sollte.

OpenID Connect-Authentifizierung (OIDC)

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseOpenIdConnectAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddOpenIdConnect“-Methode in der „ConfigureServices“-Methode auf:

    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"];
    });
    
  • Ersetzen Sie die Eigenschaft „PostLogoutRedirectUri“ in der Aktion „OpenIdConnectOptions“ durch „SignedOutRedirectUri“:

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

Authentifizierung über Facebook

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseFacebookAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddFacebook“-Methode in der „ConfigureServices“-Methode auf:

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

Authentifizierung über Google

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseGoogleAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddGoogle“-Methode in der „ConfigureServices“-Methode auf:

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

Authentifizierung über ein Microsoft-Konto

Weitere Informationen zur Microsoft-Kontoauthentifizierung finden Sie in diesem GitHub-Problem.

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseMicrosoftAccountAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddMicrosoftAccount“-Methode in der „ConfigureServices“-Methode auf:

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

Authentifizierung über Twitter

Nehmen Sie in Startup.cs die folgenden Änderungen vor:

  • Ersetzen Sie den Methodenaufruf „UseTwitterAuthentication“ in der „Configure“-Methode durch „UseAuthentication“:

    app.UseAuthentication();
    
  • Rufen Sie die „AddTwitter“-Methode in der „ConfigureServices“-Methode auf:

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

Festlegen von Standardauthentifizierungsschemas

In der Version 1.x sollten die Eigenschaften „AutomaticAuthenticate“ und „AutomaticChallenge“ der Basisklasse „AuthenticationOptions“ für ein einzelnes Authentifizierungsschema festgelegt werden. Es gab keine gute Möglichkeit, dies zu erzwingen.

In der Version 2.0 wurden diese beiden Eigenschaften als Eigenschaften der individuellen „AuthenticationOptions“-Instanz entfernt. Sie können im Methodenaufruf „AddAuthentication“ innerhalb der Methode „ConfigureServices“ von „Startup.cs“ konfiguriert werden:

services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);

Im vorherigen Codeschnipsel ist das Standardschema auf CookieAuthenticationDefaults.AuthenticationScheme („Cookie“) festgelegt.

Alternativ können Sie eine überladene Version der „AddAuthentication“-Methode verwenden, um mehr als eine Eigenschaft festzulegen. Im folgenden Beispiel für eine überladene Methode ist das Standardschema auf „CookieAuthenticationDefaults.AuthenticationScheme“ festgelegt. Das Authentifizierungsschema kann alternativ in Ihren einzelnen „[Authorize]“-Attributen oder Autorisierungsrichtlinien angegeben werden.

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

Definieren Sie ein Standardschema in Version 2.0, wenn eine der folgenden Bedingungen zutrifft:

  • Sie möchten, dass der Benutzer automatisch angemeldet wird.
  • Sie verwenden das Attribut „[Authorize]“ oder Autorisierungsrichtlinien, ohne Schemas anzugeben.

Eine Ausnahme von dieser Regel ist die „AddIdentity“-Methode. Diese Methode fügt „cookie“ für Sie hinzu und legt die Standardauthentifizierungs- und Anforderungsschemas auf die Anwendung „cookieIdentityConstants.ApplicationScheme“ fest. Darüber hinaus wird das Standardanmeldungsschema auf das externe „cookieIdentityConstants.ExternalScheme“ festgelegt.

Verwenden von HttpContext.Authentication-Erweiterungen

Die „IAuthenticationManager“-Schnittstelle ist der wichtigste Einstiegspunkt in das 1.x-Authentifizierungssystem. Es wurde durch einen neuen Satz von „HttpContext“-Erweiterungsmethoden im Namespace „Microsoft.AspNetCore.Authentication“ ersetzt.

1.x-Projekte verweisen beispielsweise auf eine „Authentication“-Eigenschaft:

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

Importieren Sie in 2.0-Projekten den Namespace „Microsoft.AspNetCore.Authentication“, und löschen Sie die „Authentication“-Eigenschaftenverweise:

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

Windows-Authentifizierung (HTTP.sys/IISIntegration)

Die Windows-Authentifizierung hat zwei Varianten:

  • Der Host lässt nur authentifizierte Benutzer zu. Diese Variante ist von den Änderungen in Version 2.0 nicht betroffen.

  • Der Host lässt sowohl anonyme als auch authentifizierte Benutzer zu. Diese Variante ist von den Änderungen in Version 2.0 betroffen. Beispielsweise sollte die Anwendung anonyme Benutzer auf der IIS- oder HTTP.sys-Ebene zulassen, Benutzer jedoch auf Controllerebene autorisieren. Legen Sie in diesem Szenario das Standardschema in der „Startup.ConfigureServices“-Methode fest.

    Legen Sie für Microsoft.AspNetCore.Server.IISIntegration das Standardschema auf „IISDefaults.AuthenticationScheme“ fest:

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

    Legen Sie für Microsoft.AspNetCore.Server.HttpSys das Standardschema auf „HttpSysDefaults.AuthenticationScheme“ fest:

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

    Ohne festgelegtes Standardschema wird verhindert, dass die Autorisierungsanforderung (Challenge) funktioniert. Dabei tritt die folgende Ausnahme auf:

    System.InvalidOperationException: Es wurde kein „authenticationScheme“ angegeben und kein „DefaultChallengeScheme“ gefunden.

Weitere Informationen finden Sie unter Konfigurieren der Windows-Authentifizierung in ASP.NET Core.

„IdentityCookie“-Optionsinstanzen

Eine Nebenwirkung der Änderungen in Version 2.0 ist die Umstellung auf benannte Optionen anstelle von „cookie“-Optionsinstanzen. Die Möglichkeit zum Anpassen der „Identitycookie“-Schemanamen wird entfernt.

Beispielsweise verwenden 1.x-Projekte die Konstruktorinjektion (Constructor Injection), um einen „IdentityCookieOptions“-Parameter an „AccountController.cs“ und „ManageController.cs“ zu übergeben. Auf das externe „cookie“-Authentifizierungsschema wird über die bereitgestellte Instanz zugegriffen:

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

Die oben erwähnte Konstruktorinjektion wird in 2.0-Projekten überflüssig, und das Feld „_externalCookieScheme“ kann gelöscht werden:

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

1.x-Projekte verwendeten das Feld „_externalCookieScheme“ folgendermaßen:

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

Ersetzen Sie in 2.0-Projekten die vorherige Codezeile durch die folgende. Die „IdentityConstants.ExternalScheme“-Konstante kann direkt verwendet werden.

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

Lösen Sie den neu hinzugefügten „SignOutAsync“-Aufruf auf, indem Sie den folgenden Namespace importieren:

using Microsoft.AspNetCore.Authentication;

Hinzufügen von IdentityBenutzer-POCO-Navigationseigenschaften

Die Entity Framework (EF) Core-Navigationseigenschaften des „IdentityUser“-Basis-POCO (Plain Old CLR Object) wurden entfernt. Wenn Ihr 1.x-Projekt diese Eigenschaften verwendet hat, fügen Sie sie dem 2.0-Projekt manuell hinzu:

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

Um doppelte Fremdschlüssel beim Ausführen von „EF Core“-Migrationen zu verhindern, fügen Sie der „OnModelCreating“-Methode Ihrer IdentityDbContext-Klassen (nach dem „base.OnModelCreating();“-Aufruf) Folgendes hinzu:

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

Ersetzen von GetExternalAuthenticationSchemes

Die synchrone „GetExternalAuthenticationSchemes“-Methode wurde zugunsten einer asynchronen Version entfernt. 1.x-Projekte enthalten den folgenden Code in „Controllers/ManageController.cs“:

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

Diese Methode erscheint auch in „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>
    }
}

Verwenden Sie in 2.0-Projekten die „GetExternalAuthenticationSchemesAsync“-Methode. Die Änderung in „ManageController.cs“ ähnelt dem folgenden Code:

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

In „Login.cshtml“ ändert sich die „AuthenticationScheme“-Eigenschaft, auf die in der „foreach“-Schleife zugegriffen wird, zu „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>
    }
}

Änderung der ManageLoginsViewModel-Eigenschaft

Ein „ManageLoginsViewModel“-Objekt wird in der Aktion „ManageLogins“ von „ManageController.cs“ verwendet. In 1.x-Projekten ist der Rückgabetyp „OtherLogins“ des Objekts „IList<AuthenticationDescription>“. Dieser Rückgabetyp erfordert einen Import von „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; }
    }
}

In 2.0-Projekten ändert sich der Rückgabetyp zu „IList<AuthenticationScheme>“. Für diesen neuen Rückgabetyp muss der „Microsoft.AspNetCore.Http.Authentication“- durch einen „Microsoft.AspNetCore.Authentication“-Import ersetzt werden.

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

Zusätzliche Ressourcen

Weitere Informationen finden Sie unter Diskussion über Auth 2.0 auf GitHub.