Sichere Web-APIs

Servicesecurity bei ASP.NET MVC 6
Kommentare

Während das Übermitteln von Benutzername und Passwort eine einfache Lösung für die Authentifizierung darstellt, erlauben Token-basierte Ansätze die Nutzung eines zentralen Benutzerpools und Single Sign-On.

Da ASP.NET MVC 6 nicht nur der Nachfolger von MVC 5 sondern auch von ASP.NET Web API ist, stellt sich die Frage, wie Entwickler die damit entwickelten Services absichern können. Als Antwort findet man derzeit im GitHub Repository des Projektteams Middleware-Komponenten für Token-basierte Verfahren sowie Basisklassen für eigene Middleware-Komponenten.

Dieser Text geht zunächst auf das einfache und weit verbreitete Verfahren HTTP Basic ein und zeigt anschließend, wie Entwickler dafür Unterstützung schaffen können. Danach wendet er sich dem Thema der Token-basierten Authentifizierung zu. Im Zuge dessen wird aufgezeigt, wie sich Clients bei einem Service unter Vorlage von Tokens – die zum Beispiel via OAuth 2.0 oder OpenID Connect bezogen wurden – authentisieren können.

Die hier besprochenen Aspekte beziehen sich auf die Version Beta 3 von ASP.NET 5 und können sich somit bis zur finalen Veröffentlichung noch ändern.

Benutzername und Passwort

Die wohl einfachste Möglichkeit, sich gegenüber einem Service zu erkennen zu geben, ist das Übermitteln einer Benutzername/Passwort-Kombination. Das kann auf verschiedene Arten erfolgen, allerdings hat sich hierfür der Standard HTTP Basic eingebürgert. Er sieht vor, dass der Aufrufer über die Kopfziele Authorization einen Base64-kodierten String mit einem Benutzernamen und einem Passwort an den Server sendet. Als Trennzeichen zwischen Benutzername und Passwort kommt ein Doppelpunkt zum Einsatz. Um anzuzeigen, dass HTTP Basic verwendet wird, ist diesem String das Wort Basic voranzustellen:

Authorization: Basic bWF4OmdlaGVpbQ==

In diesem Beispiel steht hinter dem Base64-String bWF4OmdlaGVpbQ== der Wert max:geheim, der angibt, dass als Benutzername max sowie als Passwort geheim zu nutzen ist. Da Benutzername und Passwort ohne Verschlüsselung übertragen werden, ist es üblich, HTTP Basic gemeinsam mit HTTPS zu nutzen.

Da ASP.NET 5 derzeit noch keine Unterstützung für HTTP Basic mit sich bringt, ist der Entwickler angehalten, sich selbst darum zu kümmern. Aufgrund der Einfachheit dieses Verfahrens sollte das jedoch keine allzu große Hürde darstellen. Die nächsten Abschnitte zeigen, wie diese Aufgabe bewerkstelligt werden kann.

Middleware-Komponenten

ASP.NET 5 sieht ähnlich wie sein Vorgänger vor, dass jede Anfrage sowie jede Antwort eine Pipeline bestehend aus Middleware-Komponenten durchläuft. Diese Middleware-Komponenten kümmern sich um allgemeine Aufgaben, wie zum Beispiel die Authentifizierung von Benutzern oder das Delegieren an ein Framework, wie ASP.NET MVC 6 oder ASP.NET SignalR. Durch das Auslagern von Routinen in solche Middleware-Komponenten kann der Entwickler frameworkunabhängigen Code bereitstellen.

Für Middleware-Komponenten, die sich um die Authentifizierung von Benutzern kümmern, beinhaltet ASP.NET 5 ein paar Basisklassen (Abb. 1).Diese nutzt das Produktteam auch für eigene Implementierungen, die es zusammen mit ASP.NET ausliefert. Subklassen von AuthenticationOptions stellen per Definition Konfigurationsoptionen zur Verfügung.

Die eigentlichen Authentifizierungsroutinen finden sich in Subklassen von AuthenticationHandler. Bei AuthenticationMiddleware handelt es sich um die eigentliche Middleware-Komponente, die der Entwickler in der Pipeline platziert. In der Regel delegiert sie lediglich an den AuthenticationHandler. Die zu überschreibende Methode CreateHandler kümmert sich um die Instanziierung dieses Handlers.

Wenn der Entwickler von AuthenticationHandler erbt, ist er angehalten, drei Methoden zu überschreiben: AuthenticateCore, ApplyResponseGrant und ApplyResponseChallenge. Erstere ruft ASP.NET auf, um den aktuellen Benutzer zu authentifizieren. Nach einer erfolgreichen Erledigung dieser Aufgabe retourniert sie ein AuthenticationTicket mit einer ClaimsIdentity, die den aktuellen Benutzer beschreibt. Zudem kommen so genannte Claims zum Einsatz, die im einfachsten Fall Name/Wert-Paare darstellen.

War die Authentifizierung nicht erfolgreich, weist die ClaimsIdentity des retournierten AuthenticationTicket den Wert null auf. Unabhängig davon, ob die Authentifizierung erfolgreich war, setzt ASP.NET die Abarbeitung der Anfrage fort. Somit muss die Autorisierung des Benutzers an anderer Stelle erfolgen. Dies ist notwendig, da in den meisten Fällen der Anwendungscode entscheiden muss, welche Aktionen der aktuelle Benutzer wahrnehmen darf, bzw. ob auch nicht authentifizierten Benutzern Zugriff zu gewähren ist.

Abb. 1: Basisklassen für Security-Middleware-Komponenten in ASP.NET 5

Abb. 1: Basisklassen für Security-Middleware-Komponenten in ASP.NET 5

Standardmäßig befinden sich AuthenticationMiddleware-Komponenten im aktiven Modus. Das bedeutet, dass ASP.NET die Methode AuthenticateCore im Zuge jeder Anfrage ausführt. Im passiven Modus, den der vorliegende Text nicht näher betrachtet, kommt AuthenticateCore hingegen nur auf Wunsch des Anwendungscodes zur Ausführung.

Die beiden Methoden ApplyResponseGrant und ApplyResponseChallenge ruft ASP.NET in jedem Fall nach dem Abarbeiten des Anwendungscodes auf, damit sie die Antwort bei Bedarf verändern können. Erstere ist ein Einsprungspunkt für Logik, die nach einer erfolgreichen Authentifizierung auszuführen ist. Diese Logik kann zum Beispiel das Erzeugen eines Sitzungs-Cookies beinhalten. Bei ApplyResponseChallenge handelt es sich um einen weiteren Einsprungspunkt. Er dient dem Ausführen von Routinen, nachdem der Benutzer aufgrund seiner Rechte vom Anwendungscode zurückgewiesen wurde. Solch ein Umstand ist in der Regel am HTTP-Status-Code 401 (Unauthorized) zu erkennen. In diesem Fall könnte die Methode ApplyResponseChallenge den Aufrufer auffordern, sich anzumelden. Bei Services setzt sie dazu entsprechende HTTP-Header; bei einer Webanwendung leitet sie den Anwender für gewöhnlich auf eine Login-Maske um.

Middleware für HTTP Basic

Nachdem der vorangegangene Abschnitt die Ideen hinter Authentication-Middleware-Komponenten besprochen hat, geht dieser Abschnitt auf eine Implementierung für das Verfahren HTTP Basic ein. Den Quellcode dazu findet man auf GitHub. Wie Abbildung 2 zeigt, weist diese Implementierung für jede der erwähnten Basisklassen eine Subklasse auf. Die Klasse HttpAuthenticationOptions, die die Middleware konfiguriert, verweist über ihre Eigenschaft ValidateCredentials auf eine Methode zum Prüfen von Benutzername/Passwort-Paaren. Es handelt sich dabei um eine Func<string,string,bool>, die einen Benutzernamen sowie ein Passwort entgegennimmt und true liefert, wenn ein dazugehöriger Benutzer existiert.

Die Methode AuthenticateCore des HttpBasicAuthenticationHandlers implementiert HTTP Basic und delegiert zur Validierung der aus dem Authorization-Header entnommenen Daten an ValidateCredentials. ApplyResponseGrant führt hingegen keine Aufgaben aus und die Methode ApplyResponseChallange prüft lediglich, ob der Anwendungscode den Statuscode auf 401 gesetzt hat. In diesem Fall fügt sie der Antwort die Kopfzeile

WWW-Authenticate: Basic

hinzu. Damit weist sie den Aufrufer darauf hin, dass er sich für die gewünschte Anfrage authentifizieren muss und er dazu HTTP Basic nutzen kann.

Dem Beispiel der im Rahmen von ASP.NET 5 ausgelieferten Middleware-Komponenten folgend, weist die hier betrachtete Implementierung auch eine Klasse mit einer Erweiterungsmethode zum Registrieren der Middleware-Komponente auf. Dabei handelt es sich um die Klasse HttpBasicApplicationBuilderExt mit der Methode UseHttpBasic. Diese Methode ruft bei einer Instanz von IApplicationBuilder, die ASP.NET zum Beschreiben der Pipeline heranzieht, die Methode UseMiddleware auf. Dabei übergibt sie die HttpBasicAuthenticationMiddleware als Typparameter und das zu nutzende HttpBasicAuthenticationOptions-Objekt als Übergabeparameter:

app.UseMiddleware<HttpBasicAuthenticationMiddleware>(options);
Abb. 2: Security-Middleware-Komponente für HTTP Basic

Abb. 2: Security-Middleware-Komponente für HTTP Basic

Nutzung der HTTP-Basic-Middleware

Beim Hochfahren einer Webanwendung begibt sich ASP.NET 5 auf die Suche nach einer Klasse Startup und führt u. a. ihre Methode Configure zum Konfigurieren der Pipeline aus. Damit ASP.NET 5 die hier besprochene Middleware-Komponente in diese Pipeline aufnimmt, ruft der Entwickler innerhalb von Configure die Erweiterungsmethode UseHttpBasic auf (Listing 1). Als Argument übergibt er dabei einen Lambda-Ausdruck, der ein HttpBasicOptions-Objekt entgegennimmt und dort Konfigurationsinformationen hinterlegt. Im betrachteten Fall beschränken sich diese Informationen auf die Eigenschaft ValidateCredentials, die einen weiteren Lambda-Ausdruck zum Prüfen von Benutzername/Passwort-Paaren enthält. Zur Vereinfachung erfolgt hier lediglich eine Prüfung gegen hardkodierte Daten. Bei einer Implementierung für den Produktiveinsatz könnte der Entwickler an dieser Stelle auf eine Benutzerdatenbank zugreifen.

Listing 1
public class Startup
{
  public void Configure(IApplicationBuilder app, IHostingEnvironment env)
  {
    app.UseHttpBasic(options => 
    {
      options.ValidateCredentials = (user, pwd) =>
      {
        return user == "max" && pwd == "geheim";
      };
    });

    […]
  }
}

Um festzulegen, dass eine Action-Methode lediglich von authentifizierten Benutzern aufgerufen werden darf, annotiert der Entwickler sie mit dem Attribut Authorize. Erfolgt diese Annotation für einen Controller, gilt die genannte Einschränkung für sämtliche Action-Methoden des Controllers (Listing 2). Um Informationen über den aktuellen Benutzer zu ermitteln, greifen Action-Methoden auf die geerbte Eigenschaft User zu. Hinter dieser Eigenschaft steht ein Objekt vom Typ IPrincipal, das seit .NET 4.5 in der Regel in einen ClaimsPrincipal umgewandelt werden kann. Dieser Typ weist eine Eigenschaft Claims auf, die den aktuellen Benutzer mit Claim-Objekten beschreibt. Wie bereits eingangs erwähnt, handelt es sich dabei – vereinfacht betrachtet – um Name/Wert-Paare. Die hier betrachtete Implementierung gibt diese zur Veranschaulichung aus.

Listing 2
[Route("api/[controller]")]
[Authorize]
public class ValuesController : Controller
{
  // GET: api/values
  [HttpGet]
  public IEnumerable Get()
  {
    var user = User as ClaimsPrincipal;
    foreach(var claim in user.Claims)
    {
      Debug.WriteLine(claim.Type + ": " + claim.Value);
    }

    return new string[] { "value1", "value2" };
  }
}
[…]
}

Zum Aufrufen einer solchen Action-Methode erzeugt der Entwickler eine HTTP-Anfrage, die die eingangs beschriebene Authorization-Kopfzeile beinhaltet. Zum Testen kann der Entwickler auch einen Browser nutzen. Nachdem dessen anonymer Zugriff abgewiesen wurde, wird er den Benutzer auffordern, Benutzername und Passwort zu hinterlegen und diese unter Verwendung von HTTP Basic übersenden. Der Grund dafür ist, dass die Middleware den Browser nach einem erfolglosen Zugriff mit der Kopfzeile WWW-Authenticate zu einer Authentifizierung via HTTP Basic auffordert.

Single Sign-On mit Token

Um zu verhindern, dass ein Benutzer bei verschiedenen Anwendungen jeweils ein eigenes Benutzerkonto pflegen muss, setzt man in der letzten Zeit vermehrt auf zentrale Identity-Provider. Diese kümmern sich auch um die Authentifizierung des Benutzers und stellen daraufhin ein Security-Token aus. Aufgrund solcher Token können verschiedene Services herausfinden, um welchen Benutzer es sich handelt. Dazu müssen sie zunächst das Token validieren. Zu diesem Zweck weist es häufig eine Signatur des ausstellenden Identity-Providers auf. Neben weiteren Sicherheitsmerkmalen beinhaltet es auch Claims, die den Benutzer näher beschreiben.

Der populäre Sicherheitsstandard OAuth 2.0 sowie das darauf aufbauende Protokoll OpenID Connect beschreiben, wie sich der Benutzer solch ein Token ausstellen lassen kann. Dazu definieren sie eine Komponente, die sich Authorization Server nennt und für das Ausstellen dieser Tokens verantwortlich ist. OpenID Connect legt darüber hinaus Details zum Aufbau so genannter Identity-Tokens fest. Dabei handelt es sich um JSON Web Tokens (JWT), die Claims mittels JSON repräsentieren und vom Aussteller optional verschlüsselt und/oder signiert werden können.

ASP.NET 5 bietet mit der OAuthBearerAuthenticationMiddleware-Komponente, die sich im NuGet-Paket Microsoft.AspNet.Security.OAuthBearer befindet, Unterstützung für die Authentifizierung mit solchen Tokens. Um diese Komponente zu registrieren, ruft der Entwickler in der Methode Configure der Klasse Start die Erweiterungsmethode UseOAuthBearerAuthentication auf (Listing 3). Wie bei Authentication-Middleware-Komponenten üblich, nimmt auch diese einen Lambda-Ausdruck entgegen. Dabei handelt es sich um eine Action, deren Aufgabe im Hinterlegen von Konfigurationseinstellungen besteht.

Das betrachtete Beispiel nutzt Google als Identity-Provider. Es legt mit der Eigenschaft ValidIssuer fest, dass lediglich Tokens vom Aussteller accounts.google.com zu akzeptieren sind. Um das sicherzustellen, erwartet die Middleware einen Claim mit dem Namen iss (Issuer) und diesem Wert im Token. Um zu verhindern, dass Angreifer Tokens fälschen, prüft die Middleware auch die Signatur der übersendeten Tokens. Der hierzu zu nutzende öffentliche Schlüssel findet sich in einem OpenID-Connect-konformen Metadatendokument, das der Programmcode der Middleware über die Eigenschaft Authority mitgeteilt hat.

Darüber hinaus legt das betrachtete Beispiel über die Eigenschaft ValidAudience fest, dass nur Token, die für den Client mit der ID 482348825399.apps.googleusercontent.com ausgestellt wurden, akzeptiert werden dürfen. Diese sicherheitskritische Prüfung verhindert, dass jeder beliebige Client mit einem von Google ausgestellten Token auf den Service zugreifen kann. Aus diesem Grund sehen OAuth 2.0 und OpenID Connect vor, dass sämtliche Clients beim Authorization Server zu registrieren sind. Im Zuge dessen erhalten sie unter anderem eine Client-ID. Diese Registrierung kann bei öffentlichen Anbietern, wie Google, Twitter oder Facebook kostenfrei über eine Webanwendung erfolgen. Die hierzu von Google bereitgestellte Developer Console findet sich hier.

Listing 3
[…]
app.UseOAuthBearerAuthentication(options =>
{
  options.TokenValidationParameters.ValidIssuer = "accounts.google.com";
  options.Authority = "https://accounts.google.com";
  options.TokenValidationParameters.ValidAudience = 
    "482348825399.apps.googleusercontent.com";
});
[…]
Als Alternative zu den Eigenschaften ValidAudience und ValidIssuer stehen die Eigenschaften ValidAudiences und ValidIssuers zur Verfügung. Über diese Eigenschaften kann der Programmcode mehrere gültige Aussteller sowie Clients referenzieren.

Implementierung testen

Um die im letzten Abschnitt präsentierte Konfiguration zu testen, bezieht der Entwickler via OpenID Connect bei Google ein Token. Das geschieht am einfachsten über die Nutzung des so genannten Implicit Flows. Dabei handelt es sich um jene Spielart von OpenID Connect, die bei Single-Page-Anwendungen zum Einsatz kommt. Hierzu ruft der Entwickler den in Listing 4 gezeigten URL auf, der zur Login-Maske von Google führt. Zur besseren Lesbarkeit wurden in Listing 4 Zeilenschaltungen und Einrückungen eingefügt. Eine Beschreibung der verwendeten URL-Parameter, die via OAuth 2.0 bzw. OpenID Connect definiert sind, findet sich in Tabelle 1.

Listing 4
https://accounts.google.com/o/oauth2/auth?
    response_type=id_token
    &client_id=482348825399.apps.googleusercontent.com
    &state=[...]
    &redirect_uri=http%3A%2F%2Flocalhost%3A3749%2Findex.html
    &scope=openid%20email
    &nonce=[…]
Parameter Beschreibung
response_type Gibt an, welche Spielart (genauer: Flow) von OAuth 2.0 bzw. OpenID Connect zu nutzen ist. Der Wert id_token gibt an, dass über den Implicit-Flow ein ID-Token auszustellen ist.
client_id Die ID, mit der der Client beim Authorization Server registriert ist.
state Ein String mit Zustandsinformationen. Der String wird im Zuge der Antwort vom Authorization Server zurück an den Client gesendet.
redirect_uri Der URL, an den das ausgestellte Token zu senden ist. Der URL muss für den angegebenen Client beim Authorization Server registriert sein.
scope Gibt die Rechte an, die der Client im Namen des Benutzers wahrnehmen möchte. Kommt OpenID Connect zum Einsatz, ist hier per Definition openid anzugeben. Die zusätzliche Angabe von email gibt an, dass der Client auch die E-Mail-Adresse des Benutzers einsehen möchte.
nonce Eine kryptografisch zufällige Zeichenfolge, die zum Verhindern von Angriffen zum Einsatz kommt. Gültige Token müssen diesen Wert als Claim beinhalten.

Tabelle1: URL-Parameter für das Initiieren des Implicit Flows

Nachdem sich der Benutzer bei Google angemeldet hat, muss er bestätigen, dass er der über die Client-ID referenzierten Anwendung die über den Scope ausgedrückten Rechte zukommen lassen möchte. Tut er das, leitet Google den Benutzer auf den angegebenen Redirect-URI um und übergibt dabei im Hashfragment neben dem zuvor übersendeten State ein ID-Token:

http://localhost:3749/index.html#state=[…]&id_token=[…]&[…]

Existiert keine Webanwendung, die auf den Aufruf dieses URLs reagiert, kann der Entwickler sie zum Testen zumindest mit Tools wie Fiddler auslesen. Mit dem auf diese Weise erhaltenen ID-Token kann nun der Service aufgerufen werden. Dazu ist es gemeinsam mit dem Authentifizierungstyp Bearer im Authorization-Header der Anfrage zu inkludieren:

Authorization: Bearer ...Token...

Die OAuthBearerAuthenticationMiddleware übernimmt sämtliche Claims aus dem übersendeten ID-Token in den ClaimsPrincipal, den ASP.NET MVC 6 über die geerbte Eigenschaft User anbietet. Aus diesem Grund sollte man im hier beschriebenen Fall die bei Google hinterlegte E-Mail-Adresse sowie die von Google für das Benutzerkonto verwendete ID als Claims wiederfinden.

Tokens anfordern

Single-Page-Anwendungen können ID-Tokens mit dem im letzten Abschnitt gezeigten Implicit Flow anfordern. OpenID Connect verpflichtet den Client, die empfangenen Tokens einigen Prüfungen zu unterziehen. Mit diesen Prüfungen sollen Angriffe verhindert werden. Eine Implementierung hierfür, die sich auf JavaScript und AngularJS stützt, stellt der Autor auf GitHub zur Verfügung.

Für klassische, auf Postbacks basierte Websites, bietet Microsoft hingegen über das NuGet-Paket Microsoft.AspNet.Authentication.OpenIdConnect eine Middleware-Komponente an, die ein Login via OpenID Connect ermöglicht. Ein Beispiel dazu findet sich auf GitHub. Native-Clients sind hingegen angehalten, ein Browser-Control einzublenden, das die Login-Seite des Authorization Servers zeigt. Diese Vorgehensweise dürfte den meisten Visual-Studio-Benutzern bekannt sein, zumal die Authentifizierung von Entwicklern aus Visual Studio heraus auf dieselbe Weise erfolgt.

Bei klassischen Websites und nativen Clients kommt im Gegensatz zu Single-Page-Anwendungen die etwas komplexere jedoch auch mit mehr Sicherheitsmerkmalen bestückte Spielart Authorization Code Grant zum Einsatz. Google beschreibt diese unter dem Name Server Flow.

Eigene Identity-Provider

Während sich für öffentliche Webangebote Identity-Provider, wie Google, Facebook oder Twitter anbieten, wird man bei unternehmensinternen Projekten stattdessen eigene Identity-Provider einsetzen wollen. Hierzu bieten sich Produkte wie Active Directory Federation Services oder Azure Active Directory an. Eine leichtgewichtige Middleware-Komponente zur Erstellung eines eigenen OpenID-Connect-konformen Authorization Servers mit ASP.NET 5 findet man auf GitHub. Es handelt sich dabei um den Fork einer bei Microsoft entwickelten OAuth-2.0-Middleware.

Fazit

ASP.NET 5 bietet einige Basisklassen für die Entwicklung von Middleware-Komponenten zur Authentifizierung von Benutzern. Eine Unterstützung für das weit verbreitete Verfahren HTTP Basic kann der Entwickler damit einfach schaffen. Daneben unterstützt ASP.NET 5 mit der OAuthBearerAuthenticationMiddleware eine Token-basierte Authentifizierung bei Services. Diese ist in der Lage, den Aufrufer via JWT-Tokens zu authentifizieren.

Als Authorization Server kann der Entwickler öffentliche Anbieter wie Google, Facebook oder Twitter nutzen, oder auf unternehmensinterne Lösungen wie Active Directory Federation Services zugreifen. Für das Entwickeln eigener Authorization Server stellt die Community leichtgewichtige Middleware-Komponenten zur Verfügung.

Aufmacherbild: Lock and electronic printed circuit board with technology style against fiber optic background via Shutterstock.com / Urheberrecht: asharkyu

Unsere Redaktion empfiehlt:

Relevante Beiträge

Meinungen zu diesem Beitrag

X
- Gib Deinen Standort ein -
- or -