Membership-System mit neuen Möglichkeiten

Neuerungen in ASP.NET Identity 2.0
Kommentare

Nur wenige Monate nach der Veröffentlichung der ersten Version von ASP.NET Identity bringt Version 2.0 nun zusätzliche Möglichkeiten wie das Bestätigen von E-Mail-Adressen und Telefonnummern, das Zurücksetzen von Passwörtern und die Zwei-Faktor-Authentifizierung.

Im Oktober 2013 hat Microsoft mit ASP.NET Identity eine Ablöse für das nach wie vor in ASP.NET integrierte Membership-System veröffentlicht. Neben der Verwaltung von Benutzerkonten ermöglicht das neue System moderne Sicherheitsszenarien wie das Speichern von Claims oder das Verknüpfen mit externen Benutzerkonten. Dadurch werden im Zusammenspiel mit den Security-Middleware-Komponenten von Katana Szenarien wie etwa „Log-in with Facebook“ machbar.

Nur wenige Monate nach der Veröffentlichung lieferte Microsoft Ende März 2014 mit ASP.NET Identity 2.0 nun zusätzliche Features aus. Darunter befinden sich neben grundlegenden Features wie dem Bestätigen von E-Mail-Adressen, dem Zurücksetzen von Passwörtern oder dem Sperren von Benutzern auch erweiterte Möglichkeiten, beispielsweise die Zwei-Faktor-Authentifizierung und der Einsatz von Security-Stamps zur Erhöhung der Sicherheit. Dieser Artikel geht nach einem minimalen Überblick über ASP.NET Identity auf die neuen Möglichkeiten von Version 2.0 ein.

ASP.NET Identity

Um in den Genuss von ASP.NET Identity zu kommen, wählt der Benutzer beim Anlegen einer neuen Webanwendung die Authentifizierungsvariante „Eigene Benutzerkonten“. Alternativ dazu kann er ASP.NET Identity auch im Nachhinein einklinken, indem er das NuGet-Paket Microsoft.AspNet.Identity.Owin einbindet.

Dreh- und Angelpunkt von ASP.NET Identity ist die Klasse UserManager. Der Typparameter TUser repräsentiert jene Klasse, mit der Benutzer dargestellt werden sollen, und muss ein Subtyp von Microsoft.AspNet.Identity.IUser sein. Die Vorlage aus Visual Studio legt zu diesem Zweck eine leere Implementierung von IUser namens ApplicationUser an. Dank Entity Framework kann der Entwickler diese Klasse um beliebige Eigenschaften erweitern, die dann auch in der Datenbank gespeichert werden.Der Konstruktor von UserManager erwartet eine Instanz von IUserStore. Diese realisiert den Datenbankzugriff. Die Standardimplementierung, die von Microsoft bereitgestellt wird und sich auf Entity Framework Code Only abstützt, ist UserStore:

var userManager = new UserManager(
                    new UserStore());
var user = new ApplicationUser();
user.UserName = "Test-User";
userManager.Create(user, "myPassword");

Neben der hier gezeigten Methode Create weist der UserManager zahlreiche weitere Methoden auf, um die eingangs erwähnten Entitäten zu warten und miteinander zu verknüpfen. Beispiele für deren Anwendung finden sich unter anderem im AccountController, der Teil der Projektvorlagen von Visual Studio ist.

ASP.NET Identity einrichten

Zum Einrichten von ASP.NET Identity 2.0 muss der Entwickler lediglich die NuGet-Pakete Microsoft.AspNet.Identity.Core, Microsoft.AspNet.Identity.OWIN und Microsoft.AspNet.Identity.EntityFramework einbinden bzw. aktualisieren. Solange sich ASP.NET Identity 2.0 in der Betaphase befindet, ist dabei auf die Preview-Version zurückzugreifen. Daneben bietet das NuGet-Paket Microsoft.AspNet.Identity.Samples, das für leere ASP.NET-MVC-Projekte gedacht ist, Beispiele zum Einsatz der neuen Version von ASP.NET Identity. Dazu gehört ein aktualisierter AccountController sowie Controller zum Verwalten von Benutzern (UserAdminController), Rollen (RolesAdminController) sowie des eigenen Benutzerkontos (ManageController).

Automatisches Abmelden nach Änderung von sicherheitsrelevanten Daten

Zur Erhöhung der Sicherheit bietet ASP.NET Identity 2.0 die Möglichkeit, einen Benutzer automatisch auf allen Geräten abzumelden, nachdem er sicherheitsrelevante Informationen in seinem Benutzerkonto geändert hat. Zu solchen sicherheitsrelevanten Informationen gehört z. B. das Passwort, aber auch die Verknüpfung mit externen Benutzerkonten. Somit kann unter anderem verhindert werden, dass jemand weiterhin angemeldet bleibt, der an das Passwort des Benutzers gekommen ist. Dazu speichert ASP.NET Identity bei jedem Benutzerkonto eine GUID, die als SecurityStamp bezeichnet wird. Dieser SecurityStamp wird in verschlüsselter Form in das im Zuge der Anmeldung ausgestellte Session-Cookie aufgenommen. Ändert der Benutzer sicherheitsrelevante Informationen in seinem Konto, stellt ASP.NET Identity einen neuen SecurityStamp aus. Erkennt ASP.NET Identity bei der Validierung des Session-Cookies, dass der darin gespeicherte SecurityStamp nicht mehr mit dem im Benutzerkonto übereinstimmt, wird der Benutzer abgemeldet. Damit diese Prüfung nicht ständig durchgeführt werden muss, erscheint es sinnvoll, sie nur in einem gewissen Intervall stattfinden zu lassen.

Um eine Routine zu registrieren, die diese Prüfung durchführt, verwendet der Entwickler beim Registrieren der CookieAuthenticationMiddleware mit der Erweiterungsmethode UseCookieAuthentication den vom CookieAuthenticationProvider angebotenen Callback OnValidateIdentity. In Listing 1 kommt zu diesem Callback das Ergebnis der statischen Methode SecurityStampValidator.OnValidateIdentity hinzu. Dieses Ergebnis ist vom Typ Func und prüft im über den Parameter validateInterval angegebenen Intervall, ob der SecurityStamp im Session-Cookie noch korrekt ist. Die Funktion, die über den Parameter GenerateUserIdentityAsync angeführt wird, wird von dieser Funktion dazu genutzt, ein Identity-Objekt für den aktuellen Benutzer zu erstellen. Mit dieser Identity wird der Benutzer nach Ablauf des Intervalls erneut angemeldet. Ob dieses Merkmal den Sprung von der aktuell vorliegenden Beta- in die finale Version schafft, bleibt abzuwarten.

Zum besseren Verständnis der Funktionsweise dieses Callbacks beinhaltet Listing 2 eine benutzerdefinierte Implementierung. Es entnimmt den Claims des angemeldeten Benutzers den SecurityStamp sowie die UserId. Anschließend ruft es den aktuellen SecurityStamp über die Methode GetSecurityStampAsync ab. Stimmen die beiden SecurityStamps nicht überein, wird der Benutzer abgemeldet. Im Gegensatz zum Callback, der von SecurityStampValidator.OnValidateIdentity geliefert wird, findet diese Prüfung bei jedem Seitenaufruf und nicht nur nach Ablauf eines bestimmten Intervalls statt.

public void ConfigureAuth(IAppBuilder app) {
  app.CreatePerOwinContext(ApplicationDbContext.Create);
  app.CreatePerOwinContext(
                             ApplicationUserManager.Create);
  app.UseCookieAuthentication(new CookieAuthenticationOptions
  {
      AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
      LoginPath = new PathString("/Account/Login"),
      Provider = new CookieAuthenticationProvider
      {
          OnValidateIdentity = 
                  SecurityStampValidator
                     .OnValidateIdentity(
                     validateInterval: TimeSpan.FromMinutes(30),
                     regenerateIdentity: (manager, user) => 
                               user.GenerateUserIdentityAsync(manager))
      }
  });
  app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

  app.UseTwoFactorSignInCookie(
           DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));

  app.UseTwoFactorRememberBrowserCookie(
            DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

}
[...]
OnValidateIdentity = async (ctx) =>
{ 
    ctx.Properties.IssuedUtc
    var userManager = ctx.OwinContext.GetUserManager();

    var securityStampClaim = 
               ctx.Identity.FindFirst("AspNet.Identity.SecurityStamp");
    var userIdClaim = ctx.Identity.FindFirst(ClaimTypes.NameIdentifier);

    var reject = false;

    if (securityStampClaim != null && userIdClaim != null 
                            && userManager.SupportsUserSecurityStamp)
    {
        var userId = userIdClaim.Value;
        var securityStamp = securityStampClaim.Value;
        var stampInDb = await userManager.GetSecurityStampAsync(userId);
        if (stampInDb != securityStamp) reject = true;
    }

    if (reject)
    {
        ctx.OwinContext.Authentication.SignOut(ctx.Options.AuthenticationType);
        ctx.RejectIdentity();
    }
}
[...]

[ header = Seite 2: Bestätigung der E-Mail-Adresse ]

Bestätigung der E-Mail-Adresse

ASP.NET Identity 2 unterstützt nun die im Web mittlerweile übliche Bestätigung von E-Mail-Adressen. Dazu generiert es einen Code, den der Entwickler via E-Mail an den Benutzer sendet. Indem der Benutzer diesen Code anschließend der Webanwendung mitteilt, beweist er, dass er tatsächlich im Besitz der angeführten E-Mail-Adresse ist. Damit ASP.NET Identity die versendeten Codes nicht speichern muss, leitet es sie aus dem SecurityStamp des Benutzers und weiteren Informationen des Benutzerkontos ab. Im Falle der E-Mail-Bestätigung erzeugt es mit diesen Daten einen verschlüsselten String. Kann dieser nach der Eingabe durch den Benutzer korrekt entschlüsselt werden, geht ASP.NET Identity davon aus, dass es sich dabei um den versendeten Code handelt. Für die Ver- und Entschlüsselung verwendet ASP.NET Identity eine Implementierung des Interfaces IUserTokenProvider. Die im Lieferumfang enthaltene Standardimplementierung namens DataProtectorTokenProvider verwendet dazu den von Katana bereitgestellten DataProtectionProvider, der sich je nach Hosting-Szenario auf das Data Protection API oder auf den Machine Key abstützt.

Die vom Benutzer genannte E-Mail-Adresse kann über die Eigenschaft Email der Klasse IdentityUser gesetzt bzw. abgerufen werden. Um einen Code für die E-Mail-Bestätigung zu erhalten, ruft der Entwickler die vom UserManager bereitgestellte Methode GetEmailConfirmationTokenAsync auf:

string code = await UserManager.GetEmailConfirmationTokenAsync(userId);

Für den E-Mail-Versand ist der Entwickler selbst zuständig. Nachdem der Benutzer unter Verwendung des Codes seine E-Mail-Adresse bestätigt hat, ruft der Entwickler die vom UserManager angebotene Methode ConfirmEmailAsync auf. Dabei übergibt er die Id des Benutzers sowie den erhaltenen Code:

IdentityResult result = await UserManager.ConfirmEmailAsync(userId, code);

Diese Methode prüft den Code, und falls er als korrekt erkannt wird, hinterlegt sie in der Benutzerdatenbank die Information, dass die E-Mail-Adresse bestätigt wurde. Der Rückgabewert von ConfirmEmailAsync ist eine Instanz von IdentityResult. Diese Instanz zeigt über eine Eigenschaft Success an, ob der Code korrekt war. Eventuelle Fehlerinformationen finden sich in ihrer Auflistung Errors.

Um herauszufinden, ob die E-Mail-Adresse eines Benutzers bestätigt wurde, kann sich der Entwickler an die Methode IsEMailConfirmed wenden:

manager.IsEmailConfirmed(userId);

Alternativ dazu kann er hierzu auf die Eigenschaft EmailConfirmed der verwendeten Instanz von IdentityUser zurückgreifen.

Bestätigung der Telefonnummer

Analog zur Bestätigung der E-Mail-Adresse erlaubt ASP.NET Identity auch die Bestätigung der vom Benutzer hinterlegten Telefonnummer. Dazu wird ein Code generiert und via SMS an das Handy des Benutzers gesendet.Zum Generieren dieses Codes kommt im Gegensatz zur E-Mail-Bestätigung keine Verschlüsselung zum Einsatz. Stattdessen verwendet ASP.NET Identity 2.0 hier das unter RFC 6238 beschriebene Verfahren, das aus dem SecurityStamp und weiteren Informationen des Benutzerkontos einen kurzen numerischen Code generiert. Die Implementierung dieses Verfahrens findet sich in der Klasse Rfc6238AuthenticationService. Um zu prüfen, ob der vom Benutzer erfasste Code korrekt ist, generiert ihn ASP.NET Identity erneut und vergleicht das Ergebnis mit der Benutzereingabe. Die vom Benutzer genannte Telefonnummer kann über die Eigenschaft PhoneNumer der Klasse IdentityUser gesetzt bzw. abgerufen werden. Um einen Code zur Bestätigung dieser Telefonnummer anzufordern, ruft der Entwickler die vom UserManager angebotene Methode GenerateChangePhoneNumberTokenAsync auf und übergibt dabei die UserId sowie die erfasste Telefonnummer:

var code = await UserManager.GenerateChangePhoneNumberTokenAsync(userId, telNr);

Für das Versenden des Codes via SMS ist der Entwickler wieder selbst zuständig. Hierzu kann er z. B. auf den über Azure angebotenen Dienst Twillo zurückgreifen. Um den vom Benutzer erfassten Code zu prüfen, wendet sich der Entwickler an die Methode VerifyChangePhoneNumberTokenAsync des UserManagers. Dabei übergibt er die UserId, den Code und die Telefonnummer:

bool ok = await UserManager.VerifyChangePhoneNumberTokenAsync(userId, token, phoneNumber);

Möchte der Entwickler im Falle eines korrekten Codes die Bestätigung der Telefonnummer auch in der Datenbank hinterlegen, verwendet er hingegen die Methode ChangePhoneNumberAsync:

var result = await UserManager.ChangePhoneNumberAsync(User.Identity.GetUserId(), model.PhoneNumber, model.Code);

Um herauszufinden, ob die Telefonnummer eines Benutzers bestätigt wurde, kann sich der Entwickler an die Methode IsPhoneNumberConfirmed wenden. Alternativ dazu kann er hierzu auf die Eigenschaft PhoneNumberConfirmed der verwendeten Instanz von IdentityUser zurückgreifen.

Kennwort zurücksetzen

Neben der Bestätigung von E-Mail-Adressen hat sich im Web mittlerweile auch die Möglichkeit des Zurücksetzens von vergessenen Passwörtern eingebürgert. Dazu geben Sie Ihre E-Mail-Adresse an und erhalten anschließend eine E-Mail mit einem Code. Unter Verwendung dieses Codes können sie anschließend in der Webanwendung ein neues Passwort erstellen.

Zur Generierung des Codes für das Zurücksetzen eines Passworts verwendet ASP.NET Identity dasselbe Verfahren wird bei der Generierung eines Codes zur Bestätigung der E-Mail-Adresse. Um zu solch einem Code zu gelangen, kann der Entwickler die Methode GetPasswordResetTokenAsync des UserManagers heranziehen:

string code = await UserManager.GetPasswordResetTokenAsync(user.Id);

Die Methode ResetPasswordAsync hingegen speichert für den Benutzer das übergebene neue Passwort, sofern der ebenfalls zu übergebende Code korrekt ist:

IdentityResult result = await UserManager.ResetPasswordAsync(user.Id, model.Code, model.Password);

Zwei-Faktor-Authentifizierung

Zur Steigerung der Sicherheit von Webanwendungen unterstützt ASP.NET Identity ab Version 2.0 auch Zwei-Faktor-Authentifizierungs-Szenarien. Gemeint ist damit, dass der Benutzer nach der Authentifizierung mit seinen primären Credentials wie Benutzername und Passwort durch eine weitere Angabe von Informationen beweisen muss, dass er auch jener Benutzer ist, der er vorgibt, zu sein. Dazu könnte er aufgefordert werden, einen Code zu erfassen, den er per E-Mail oder SMS erhalten hat. Auch das Lösen von Rechenaufgaben und Captchas zum Beweis, dass es sich um einen menschlichen Benutzer und nicht um einen Automatismus handelt, fällt in den Bereich der Zwei-Faktor-Authentifizierung.

Um dem Benutzer das Leben etwas einfacher zu gestalten, erlaubt ihm ASP.NET Identity 2.0, das Ergebnis der Zwei-Faktor-Authentifizierung in Form eines Cookies zu speichern, sodass er am selben Arbeitsplatz nicht immer wieder dazu aufgefordert wird. Die einzelnen Strategien zur Durchführung einer Zwei-Faktor-Authentifizierung kapselt ASP.NET Identity in Implementierungen des Interfaces IUserTokenProvider, die im Zuge der Konfiguration (siehe unten) beim UserManager zu registrieren sind. Die aktuell vorliegende Betaversion von ASP.NET Identity 2.0 bringt zwei Implementierungen dieses Interfaces mit: Der PhoneNumberTokenProvider versendet einen Code per SMS, und der EmailTokenProvider versendet einen Code per E-Mail. Der Code wird in beiden Fällen aus dem SecurityStamp sowie weiteren Informationen des Benutzerskontos abgeleitet. Dabei kommt, wie bei der Bestätigung von Telefonnummern, das unter RFC 6238 beschriebene Verfahren zum Einsatz. Da sich ASP.NET Identity weder um das Versenden von E-Mails noch um das Versenden von SMS kümmert, kommen hierfür Implementierungen des Interface IIdentityMessageService zum Einsatz. Diese Implementierungen hat der Entwickler bereitzustellen und in den Eigenschaften EmailService und SmsService des UserManagers zu hinterlegen.Zur Nutzung der Zwei-Faktor-Authentifizierung registriert der Entwickler die CookieAuthenticationMiddleware, die auch zur Implementierung formularbasierter Sicherheit zum Einsatz kommt, zwei weitere Male: Einmal unter Verwendung des AuthenticationTypes TwoFactorCookie und einmal unter Verwendung des AuthenticationTypes TwoFactorRememberBrowserCookie:

app.UseTwoFactorSignInCookie(
         DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(5));
app.UseTwoFactorRememberBrowserCookie(
         DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);

Erstere Instanz der CookieAuthenticationMiddleware kümmert sich um das temporäre Speichern der Benutzerdaten zwischen dem Log-in mit den primären Credentials und dem Beenden der Zwei-Faktor-Authentifizierung. Letztere Instanz kümmert sich um das langfristige Speichern eines Cookies, aus dem hervorgeht, dass sich der Benutzer schon einmal an dem jeweiligen Arbeitsplatz erfolgreich  angemeldet hat und dies deswegen nicht mehr notwendig ist. Der Einsatz solcher Cookies zur Erhöhung der Benutzerfreundlichkeit ist optional und somit auch der Einsatz dieser Middleware-Instanz.Der Einsatz einer Zwei-Faktor-Authentifizierung kann pro Benutzer aktiviert bzw. deaktiviert werden. Zum Speichern bzw. Abrufen dieser Entscheidung kann der Entwickler auf die Methoden SetTwoFactorEnabled sowie GetTwoFactorEnabled des UserManagers zurückgreifen. Alternativ dazu kann er auch die Eigenschaft TwoFactorEnabled der Klasse IdentityUser heranziehen. Daneben kann der Entwickler prüfen, ob ein Cookie vorhanden ist, das eine erneute Authentifizierung unnötig macht. Dazu ruft er die Methode TwoFactorBrowserRememberedAsync des AuthenticationManagers auf:

bool ok = AuthenticationManager.TwoFactorBrowserRememberedAsync(user.Id)

Bevor er den Benutzer auffordert, sich mit sekundären Credentials erneut zu erkennen zu geben, ist der Entwickler angehalten, die durch die erste Anmeldung erhaltenen Benutzerinformationen zu speichern, damit er später noch weiß, um welchen Benutzer es sich dabei gehandelt hat. Dazu ruft er die Methode SignIn des AuthenticationManagers auf und übergibt dabei eine ClaimsIdentity, die die Id des Benutzers (und ggf. weitere Daten) sowie den AuthenticationType TwoFactorCookie beinhaltet:

ClaimsIdentity identity = 
         new ClaimsIdentity(DefaultAuthenticationTypes.TwoFactorCookie);
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
AuthenticationManager.SignIn(identity);

Dadurch wird die weiter oben besprochene CookieAuthenticationMiddleware aktiv, die mit demselben AuthenticationType registriert wurde, und erzeugt ein entsprechendes Cookie. Um die auf diesem Weg gespeicherte Identity später wieder abzurufen, übergibt der Entwickler den genannten AuthenticationType an die Methode AuthenticateAsync des AuthenticationManagers. Diese authentifiziert den Benutzer unter Verwendung der registrierten Middleware, die auf das Cookie zurückgreift, und liefert ein AuthenticationResult zurück. Dieses AuthenticationResult gewährt über ihre Eigenschaft Identity Zugriff auf die gespeicherte Identität. Die Erweiterungsmethode GetUserId hilft beim Abrufen der im Rahmen eines Claims hinterlegten Id:

var result = await AuthenticationManager.AuthenticateAsync(
                                    DefaultAuthenticationTypes.TwoFactorCookie);
    result.Identity.GetUserId();

Unter Verwendung der Methode GenerateTwoFactorTokenAsync kann der Entwickler anschließend die sekundäre Authentifizierung einleiten. Dabei übergibt er die UserId sowie einen internen Namen, mit dem ASP.NET Identity auf den zu verwendenden IUserTokenProvider schließen kann:

var token = await UserManager.GenerateTwoFactorTokenAsync(userId, provider);

Diese internen Namen werden beim Registrieren der Provider angegeben und können über UserManager.TwoFactorProviders.Keys abgerufen werden. Das Ergebnis dieser Methode ist ein Token, das für die sekundäre Authentifizierung eingesetzt wird. Ob dieses Token dem Benutzer angezeigt werden darf oder nicht, hängt vom verwendeten IUserTokenProvider ab: Beim Einsatz des PhoneNumberTokenProviders und der EmailTokenProviders entspricht dieses Token jenem dem Benutzer per SMS oder E-Mail gesendeten. Deswegen darf es dem Benutzer in diesen Fällen nicht präsentiert werden, zumal dieser durch Eingabe des Codes beweisen soll, dass er im Besitz der jeweiligen Telefonnummer bzw. E-Mail-Adresse ist. Beim Einsatz einer benutzerdefinierten IUserTokenProvider-Implementierung könnte auf diesem Weg hingegen eine dem Benutzer zu präsentierende Rechenaufgabe oder ein Captcha zurückgegeben werden. Um den vom Benutzer gelieferten Code zu prüfen, delegiert der Entwickler an die Methode VerifyTwoFactorTokenAsync des UserManagers:

bool ok = await UserManager.VerifyTwoFactorTokenAsync(userId, provider, code)

War der Code korrekt, erstellt der Entwickler für den Benutzer eine ClaimsIdentity, die sich auf die Daten aus dem Benutzerkonto stützt und den AuthenticationType ApplicationCookie aufweist. Dies kann er mit der Erweiterungsmethode GenerateUserIdentityAsync bewerkstelligen, die ASP.NET Identity für Instanzen von IdentityUser anbietet:

var userIdentity = await user.GenerateUserIdentityAsync(UserManager);

Anschließend übergibt er diese Identity an die Methode SignIn des AuthenticationManager:

AuthenticationManager.SignIn(userIdentity);

Hierdurch wird die CookieAuthenticationMiddleware, die für die formularbasierte Sicherheit verwendet wird und unter Verwendung des AuthenticationTypes ApplicationCookie registriert wurde, dazu aufgefordert, ein Session-Cookie zu erstellen.

Möchte der Entwickler den Ausgang der sekundären Authentifizierung in einem Cookie speichern, erstellt er zusätzlich eine ClaimsIdentity mit der UserId sowie dem AuthenticationType TwoFactorRememberBrowser. Hierzu kann er auf die Erweiterungsmethode CreateTwoFactorRememberBrowserIdentity zurückgreifen:

var rememberBrowserIdentity = 
       AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);

Übergibt er auch diese Identity an die Methode SignIn des AuthenticationManagers, wird hierdurch die oben beschriebene CookieAuthenticationMiddleware aktiv, die unter Verwendung des AuthenticationTypes TwoFactorRememberBrowser registriert wurde, und sie erstellt einen Cookie. Ob dieser Cookie für einen bestimmten Benutzer existiert, kann in weiterer Folge mit der Methode TwoFactorBrowserRememberedAsync geprüft werden. Durch das Implementieren von IUserTokenProvider kann der Entwickler weitere Strategien für die Zwei-Faktor-Authentifizierung bereitstellen. Informationen dazu finden sich hier.

[ header = Seite 3: Benutzername und Passwort validieren ]

Benutzername und Passwort validieren

Um festzulegen, wie Benutzernamen und Passwörter zu prüfen sind, kann der Entwickler seit den ersten Tagen von ASP.NET Identity dem UserManager einen UserValidator sowie einen PasswordValidator zuweisen (Listing 3). In beiden Fällen handelt es sich um eine Implementierung von IIdentityValidator. Ab Version 2 bietet die Standardimplementierung zum Validieren von Passwörtern mehr Validierungsoptionen (Listing 3).

public static ApplicationUserManager Create(
                      IdentityFactoryOptions options, 
                      IOwinContext context)
{
  var manager = new ApplicationUserManager(
                           new UserStore(
                                 context.Get()));

  manager.UserValidator = new UserValidator(manager)
  {
      AllowOnlyAlphanumericUserNames = false,
      RequireUniqueEmail = true
  };

  manager.PasswordValidator = new PasswordValidator
  {
      RequiredLength = 6, 
      RequireNonLetterOrDigit = true,
      RequireDigit = true,
      RequireLowercase = true,
      RequireUppercase = true,
  };

  manager.RegisterTwoFactorProvider("PhoneCode", 
                 new PhoneNumberTokenProvider
  {
      MessageFormat = "Your security code is: {0}"
  });
  manager.RegisterTwoFactorProvider("EmailCode", 
                 new EmailTokenProvider
  {
      Subject = "SecurityCode",
      BodyFormat = "Your security code is {0}"
  });

  manager.UserLockoutEnabledByDefault = true;
  manager.DefaultAccountLockoutTimeSpan = TimeSpan.FromMinutes(5);
  manager.MaxFailedAccessAttemptsBeforeLockout = 5;

  manager.EmailService = new EmailService();
  manager.SmsService = new SmsService();
  var dataProtectionProvider = options.DataProtectionProvider;
  if (dataProtectionProvider != null)
  {
      manager.UserTokenProvider = 
             new DataProtectorTokenProvider(
                        dataProtectionProvider.Create("ASP.NET Identity"));
  }
  return manager;
}

Benutzerkonten sperren

Zur Erhöhung der Sicherheit ist es mittlerweile bei vielen Webangeboten üblich, ein Benutzerkonto für eine bestimmte Zeitspanne zu sperren, nachdem mehrmals hintereinander ein Anmeldeversuch mit einem falschen Passwort erfolgte. ASP.NET Identity 2.0 unterstützt dies, indem es dem Entwickler die Möglichkeit bietet, pro Benutzerkonto die Anzahl derartiger fehlgeschlagener Anmeldeversuche zu protokollieren. Dazu bietet es dem Entwickler die Methode AccessFailed, die einen internen Zähler inkrementiert:

await UserManager.AccessFailed(user.Id);

Mit ResetAccessFailedCount kann der Entwickler diesen Zähler für einen anzugebenden Benutzer wieder zurücksetzen und GetAccessFailedCount liefert seinen Wert. Ab wie vielen fehlerhaften Versuchen ein Konto zu sperren ist, führt der Entwickler bei der Konfiguration des UserManagers über die Eigenschaft MaxFailedAccessAttemptsBeforeLockout an. Die standardmäßige Sperrdauer gibt er hingegen über die Eigenschaft DefaultAccountLockoutTimeSpan bekannt (Listing 3).Ob ein Benutzer gesperrt ist, kann der Entwickler über die vom UserManager angebotene Methode IsLockedOut in Erfahrung bringen. Mit den Methoden GetLockoutEndDate und SetLockoutEndDate kann er hingegen herausfinden, bis wann der Benutzer gesperrt ist bzw. die Sperre aktualisieren.

Damit ein Benutzer überhaupt gesperrt werden kann, muss die Möglichkeit zum Sperren für sein Benutzerkonto erst einmal aktiviert werden. Dazu verwendet der Entwickler die Methode SetLockoutEnabled. Zum Auslesen dieser Einstellung zieht er GetLockoutEnabled heran. Setzt der Entwickler beim Konfigurieren des UserManagers die Option UserLockoutEnabledByDefault auf true, wird sie für jeden neuen Benutzer automatisch aktiviert. Ist diese Option deaktiviert, wird der interne Zähler zwar trotzdem mitgeführt, allerdings wirkt er sich dann nicht auf das Ergebnis von IsLockedOut aus. Alternativ zu den genannten Methoden des UserManagers kann der Entwickler auch auf Eigenschaften der betroffenen IdentityUser-Instanz zugreifen. Diese nennen sich LockoutEnabled, LockoutEndDateUtc und AccessFailedCount.

Da der Entwickler selbst für die Verwaltung des internen Zählers verantwortlich ist, kann er entscheiden, wann dieser zu inkrementieren und wann er auf Null zurückzusetzen ist. Somit kann er den Zähler auch im Rahmen eines sekundären Log-ins aktualisieren oder wenn falsche Codes zum Zurücksetzen des Passworts verwendet werden.

Konfiguration

Listing 1 zeigt eine idealtypische Konfiguration für ASP.NET Identity 2.0, die aus dem Beispielprojekt im NuGet-Paket Microsoft.AspNet.Identity.Samples entnommen wurde. Es registriert mit der Erweiterungsmethode CreatePerOwinContext eine Middleware, um pro Seitenaufruf eine Instanz von ApplicationDbContext sowie eine Instanz von ApplicationUserManager bereitzustellen. Bei ersterer Instanz handelt es sich um den Entity-Framework-basierten DbContext für den Zugriff auf die Benutzerdatenbank durch den UserManager; bei ApplicationUserManager handelt es sich um die hier verwendete Subklasse von UserManager. Dies ermöglicht den globalen Zugriff auf einen konfigurierten UserManager und dessen DbContext über die im OWIN-Kontext angebotene Erweiterungsmethode Get. Der Typparameter T steht dabei für den Typ der gewünschten Instanz:

HttpContext.GetOwinContext().Get();

Alternativ dazu kann der Entwickler zum Zugriff auf den UserManager auch die Erweiterungsmethode GetUserManager verwenden:

HttpContext.GetOwinContext().GetUserManager();

Damit die von CreatePerOwinContext registrierte Middleware die genannten Instanzen erstellen kann, übergibt Listing 1 eine Factory-Methode. Die Factory-Methode zum Erzeugen des UserManagers findet sich in Listing 3. Sie instanziiert einen neuen ApplicationUserManager unter Verwendung des vorhin besprochenen DbContext, der mit der Methode Get des OWIN-Kontexts abgerufen wird. Anschließend registriert das betrachtete Beispiel beim UserManager einen UserValidator und einen PasswortValidator sowie zwei IUserTokenProvider für die Zwei-Faktor-Authentifizierung mittels RegisterTwoFactorProvider. Darüber hinaus registriert es einen EmailService sowie einen SmsService, den der UserManager zum Versenden von E-Mails und SMS heranzieht. Bei den hier verwendeten Instanzen handelt es sich um Implementierungen von IIdentityMessageService. Ein Beispiel dafür findet sich in Listing 4.

Außerdem registriert das betrachtete Listing einen UserTokenProvider, der – wie weiter oben diskutiert – zum Generieren von Tokens dient, die ASP.NET Identity für das Bestätigen von E-Mail-Adressen sowie für das Zurücksetzen von Passwörtern per E-Mail versendet. Bei diesem UserTokenProvider handelt es sich um eine Implementierung des Interfaces IUserTokenProvider. Als Implementierung kommt an dieser Stelle der von ASP.NET Identity bereitgestellte DataProtectorTokenProvider zum Einsatz, der sich auf den von Katana bereitgestellten DataProtectionProvider zum einfachen Ver- und Entschlüsseln von Daten abstützt. Diesen erhält es durch Aufruf der Methode Create, die über die Eigenschaft DataProtectionProvider des an die Factory-Methode übergebenen Objektes options angeboten wird. Der dabei übergebene String ASP.NET Identity dient der Ableitung eines kryptografischen Schlüssels.

public class EmailService : IIdentityMessageService
{
  public async Task SendAsync(IdentityMessage message)
  {
      var mailMessage = new MailMessage(
          "manfred.steyer@gmx.net",
          message.Destination,
          message.Subject,
          message.Body
          );

      var client = new SmtpClient();
      client.UseDefaultCredentials = false;
      client.Credentials = new NetworkCredential("username", "password");
      client.EnableSsl = true;
      client.Host = "mail.gmx.net";
      client.Port = 587;

      client.Send(mailMessage);
  }
}

Weitere Neuerungen

Neben den bis dato betrachteten neuen Möglichkeiten stellt der UserManager ab ASP.NET Identity 2.0 über die Eigenschaft Users ein IQueryable zum Abfragen von Benutzerkonten bereit. Analog dazu bietet die Klasse RoleStore, die das Verwalten von Rollen erlaubt, ein IQueryable Roles an. Darüber hinaus erzeugt ASP.NET Identity 2.0 einen eindeutigen Index für den Benutzernamen. Die Methode Delete wird nun zum Löschen von Benutzern angeboten, und der Entwickler kann den Typ des Primärschlüssels der eingerichteten Tabellen durch den Einsatz von Typparametern selbst bestimmen. Standardmäßig kommen Strings zum Einsatz.

Implementierung eines eigenen UserStores

Möchte sich der Entwickler zur Speicherung der Benutzerdaten nicht auf Entity Framework abstützen, kann er einen eigenen UserStore implementieren. Damit dieser Verwendung findet, ist er an den Konstruktor von UserManager zu übergeben. Bei diesem UserStore handelt es sich um eine Implementierung von IUserStore. Da durch die Implementierung dieses Interfaces nur die wichtigsten Datenbankzugriffe für den UserManager bereitgestellt werden, war der Entwickler in der Vergangenheit dazu angehalten, einen UserStore zu realisieren, der neben IUserStore auch die Interfaces IUserLoginStore, IUserClaimStore, IUserRoleStore und IUserPasswordStore realisiert. Um die neuen Möglichkeiten zu unterstützen, muss der UserStore darüber hinaus auch folgende Interfaces realisieren: IUserSecurityStampStore, IQueryableUserStore, IUserEmailStore, IUserPhoneNumberStore, IUserTwoFactorStore und IUserLockoutStore TKey>.

TUser steht hierbei für den Typ der zu verwendenden IdentityUser-Instanz und TKey für den Typ, der für die Primärschlüssel heranzuziehen ist. Von den hier genannten Interfaces existieren darüber hinaus auch Interfaces, die TKey auf string fixieren.

Breaking Changes

Bei der Migration von Version 1 auf die neue Version 2 ist der Entwickler dazu angehalten, das verwendete Datenbankschema zu aktualisieren. Informationen dazu finden sich hier.

Fazit

Mit Version 2.0 liefert Microsoft wenige Monate nach dem Erscheinen der ersten Version von ASP.NET Identity wichtige Features nach. Darunter fallen das Bestätigen von E-Mail-Adressen, das Zurücksetzen von Passwörtern und das Sperren von Benutzern. Darüber hinaus wird die Sicherheit durch die Zwei-Faktor-Authentifizierung und den Einsatz von Security-Stamps erhöht. Positiv fällt dabei auf, dass durch ein flexibles Design das Austauschen und Erweitern bestehender Konzepte erlaubt wird. Dies betrifft unter anderem das Versenden von E-Mails sowie SMS-Nachrichten und das Bereitstellen weiterer Strategien für die Zwei-Faktor-Authentifizierung.

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -