Thomas Bandt

Über mich | Kontakt | Archiv

ASP.NET MVC - Login-Check für Ajax-Requests erweitern

Das Konzept der FormsAuthentication, welches bereits mit ASP.NET 1.0 anno 2002 eingeführt wurde, hat auch in ASP.NET MVC Bestand (auch wenn man auf Membership-, Profile-Provider und das andere Gedöns heute getrost verzichten kann/sollte). Es wurde ganz elegant übernommen, in dem man die Authentifizierungs-Prüfung über Attribute jeweils auf Action- oder Controller-Ebene regeln kann:

   1:  [Authorize]
   2:  public ActionResult Index()
   3:  {
   4:      return View();
   5:  }

Ist das Ticket, sprich Cookie, abgelaufen oder die Anmeldung aus sonstigen Gründen ungültig, wird automatisch zum Login weitergeleitet - diese Seite lässt sich auch wie bisher per web.config festlegen:

   1:  <authentication mode="Forms">
   2:      <forms loginUrl="~/account/login" timeout="120" />
   3:  </authentication>

Der Vorgang ist also wie folgt:

  1.  Benutzer ruft eine Seite im Browser auf
  2. Authentifizierung wird geprüft
  3. Wenn alles ok ist, wird der Inhalt ausgeliefert
  4. Wenn keine Authentifizierung möglich ist, wird zur Login-Seite weitergeleitet (302-Redirect)

Das ist soweit auch ganz praktisch, es führt nur zu einem Problem: Bei der Verwendung von Ajax-Requests greift dieser Mechanismus ebenfalls. Und da diese bekanntermaßen "im Hintergrund" ablaufen, wird zwar der Request, der vom Script aus gefeuert wird, zur Login-Seite weitergeleitet - der Benutzer vor dem Bildschirm bekommt davon aber nichts mit. Für ihn passiert im besten Fall gar nichts, d.h. seine Aktion bleibt ohne Folgen und er wundert sich warum, im schlimmsten Fall fliegen ihm Scriptfehler um die Ohren (soll ja Leute geben, die noch den IE benutzen).

In meinen Augen sollte es keinen Unterschied machen, ob der Benutzer eine Aktion über einen "normalen" Request, sprich Seitenaufruf, ausführt oder ob Ajax verwendet wird - z.B. beim Löschen von Datensätzen in einem Grid. Schlägt die Authentifizierung fehl, sollte das Verhalten konsistent sein und jeweils mit einem Hinweis zum Login weitergeleitet werden.

Die Lösung des Ganzen besteht erwartungsgemäß aus zwei Teilen, einem auf der Serverseite und einem auf der Clientseite.

Auf der Serverseite erstellt man einfach einen eigenen Filter, der vom AuthorizeAttribute ableitet und somit die volle Funktionalität behält. Es ist hier dann auch nicht mehr nötig von Hand zu prüfen, ob die Anfrage gültig ist oder nicht, das tut ja das AuthorizeAttribute bereits. Interessant wird es dann bei der Behandlung des Fehlers:

   1:  public class AuthorizeLoginAttribute : AuthorizeAttribute
   2:  {
   3:      protected override void HandleUnauthorizedRequest(AuthorizationContext filterContext)
   4:      {
   5:          if (filterContext.HttpContext.Request.IsAjaxRequest())
   6:          {
   7:              filterContext.HttpContext.Response.StatusCode = 401;
   8:              filterContext.HttpContext.Response.TrySkipIisCustomErrors = true;
   9:              filterContext.HttpContext.Response.End();
  10:          }
  11:          base.HandleUnauthorizedRequest(filterContext);
  12:      }
  13:  }

Über die Erweiterungsmethode IsAjaxRequest(), die vom Mvc-Framework mitgeliefert wird, wird geprüft, ob es ein Ajax-Request ist. Anschließend wird der Status-Code auf 401 gesetzt (Unauthorized) und, ganz wichtig, festgelegt, dass die Standardfehlerbehandlung, wie in der Web.config definiert, nicht verwendet wird. Nur so umgeht man hier die automatisch greifende Weiterleitung.

Die Verwendung geschieht erfolgt zu den Standard-Attributen:

   1:  [AuthorizeLogin]
   2:  public ActionResult Index()
   3:  {
   4:      return View();
   5:  }

Auf der Clientseite dachte ich zunächst daran das Ergebnis bei jedem Request zu prüfen und dann weiterzuleiten - aber es ist mit Hilfe von jQuery noch einfacher als auf der Serverseite:

   1:  $().ready(function () {
   2:      $('#AppStatusBar').ajaxError(function (xhr, status, err) {
   3:          if (status.status == 401)
   4:              window.location.href = '/account/login?ReturnUrl=' + window.location.pathname;
   5:      });
   6:  });

Es wird einfach ein globaler Fehler-Handler registriert, der für jeden Fehler, der bei Ajax-Aufrufen passiert, greift. Man bindet das Event einfach an irgendein Element, was auf jeder Seite vorhanden ist, bei mir ist das im Beispiel ein Div mit der ID AppStatusBar.

Wenn der Statuscode 401 ist, wird zum Login weitergeleitet - und zwar mit Angabe der lokalen URL, damit auf diese im Anschluss weitergeleitet werden kann, wie es auch bei "normalen" Requests der Fall ist.

That's it.

Kommentare

  1. Thomas goes .NET schrieb am Dienstag, 11. Januar 2011 13:09:00 Uhr:

    Analog zum Beispiel mit dem Login-Check sollte man auch bei allen anderen Fehlern, die bei Ajax-Aufrufen passieren, den Benutzer nicht im Regen stehen lassen. Im klassischen Fall eines GET- oder POST-Aufrufs erhält man ja schließlich auch eine Fehler ...


« Zurück  |  Weiter »