OAuth 2.0 & Basic Authentication in derselben Web API

6. Juli 2016

In einer ASP.NET Web API 2-Anwendung standen wir vor dem Problem pro Operation jeweils eine von zwei alternativen Authentifizierungsmethoden zu unterstützen: die Azure Active Directory-Bearer-Authentifizierung (OAuth 2.0) und Basic Authentication. Der naheliegende Ansatz, die Azure AD-Authentifizierung durch die OWIN-Middleware [1] [2] und die Basic Authentication als Web API-Authentifizierungsfilter [3] zu implementieren funktioniert jedoch nicht. Aus diesem Grund habe ich nach einer anderen Lösung suchen müssen.

Einstellung der Authentifizierung

Zur Authentifizierung am Azure AD wurde eine Implementierung aus der Microsoft.Owin.Security.ActiveDirectory-Assembly verwendet, die in der Startup.Auth-Datei wie folgt konfiguriert wurde:

   1: public void ConfigureAuth(IAppBuilder app)

   2: {

   3:     app.UseWindowsAzureActiveDirectoryBearerAuthentication(

   4:         new WindowsAzureActiveDirectoryBearerAuthenticationOptions

   5:         {

   6:             AuthenticationType = AuthenticationTypes.AzureActiveDirectory,

   7:             Tenant = ...,

   8:             TokenValidationParameters = new TokenValidationParameters

   9:             {

  10:                 ...

  11:             }

  12:         }

  13:     );

  14: }

Für die Basic Authentication wurde unsere eigene Implementierung verwendet, die als ein IAuthenticationFilter-Attribut [3] realisiert wurde („ConfiguredBasicAuthenticationAttribute“).

Einsatz im Controller

In folgendem Controller musste die GET-Methode Azure AD-Authentication und die POST-Methode Basic Authentication verwenden:

   1: [Authorize]

   2: public class UsersController : ApiController

   3: {

   4:     // Soll nur Bearer Authentication verwenden

   5:     public async Task Get()

   6:     {

   7:         ...

   8:     }

   9:

  10:     // Soll nur Basic Authentication verwenden

  11:     [OverrideAuthentication]

  12:     [ConfiguredBasicAuthentication(...)]

  13:     public async Task Post(...)

  14:     {

  15:         ...

  16:     }

  17: }

Das Authorize-Attribut ohne Angabe von Benutzernamen oder -Rollen tut nichts weiter als allen nichtauthentifizierten Benutzern den Zugriff auf den ganzen Controller zu verweigern. Da die Azure AD Authentication im Rahmen der OWIN-Middleware bereits global registriert wurde, war der ursprüngliche Gedanke diese direkt bei der POST-Methode durch die Basic Authentication zu ersetzen.

Um dies zu verwirklichen war die Azure AD Authentication zu überbrücken, um anschließend unsere ConfiguredBasicAuthentication zu erzwingen. Der OverrideAuthentication-Filter [4], wie der Name sagt, sollte für die Überbrückung aller bisher registrierten Authentifizierungsmethoden sorgen, um den Einsatz des ConfiguredBasicAuthentication-Authentifizierungsattributs zu ermöglichen.,

Dieser Controller hat jedoch die gewünschte Funktionalität nicht aufgewiesen. Die POST-Methode hat nämlich trotz der Angabe des OverrideAuthentication-Attributs beide Authentifizierungsmethoden zugelassen, als würde die Überbrückung überhaupt nicht funktionieren.

Warum es nicht funktioniert

Warum dieser Ansatz nicht funktioniert hat, wird einem klar, wenn man unter die Haube schaut, wie eine HTTP-Anfrage im Zusammenhang mit Authentifizierung und Autorisierung verarbeitet wird. Um über den Zugang eines Benutzers auf eine geschützte Ressource eine Entscheidung treffen zu können, sind im Regelfall folgende zwei Schritte erforderlich:

  1. Authentifizierung: Feststellung der Identität des Benutzers. (“Wer ist dieser Benutzer?”)
  2. Autorisierung: Ermittlung der Zugangsrechte des authentifizierten Benutzers (“Darf er das machen?”)

In unserer Anwendung hat die Authentifizierung an zwei verschiedenen Stellen stattgefunden:

  1. Azure AD-Authentifizierung in der Host-Schicht im Rahmen der Katana-Middleware (Microsofts Implementierung von OWIN). Diese erfolgt beim Eingang von HTTP-Anfragen und ist unabhängig von der Web API-Schicht.
  2. Basic Authentication in der Web API-Schicht als Authentication Filter.

Diesen Ablauf veranschaulicht folgende Abbildung:

Abbildung 2

Abbildung 1 – Die von uns verwendeten Teile der Web API 2-Pipeline

Das Override-Attribut hat nicht den durch die Azure AD-Authentifizierung bereits gesetzten Principal überschrieben, weil es nur Authentifizierungsfilter aus der Web API-Schicht außer Kraft setzen kann. Falls eine HTTP-Anfrage Azure AD-Authentifizierung verwendet hat, wurde der Principal von der Katana-Middleware vor der Verarbeitung in der Web API-Schicht gesetzt. Wenn keine Basic Authentication in der Anfrage vorhanden war, hat der Web API-Filter lediglich keinen Principal gesetzt und der bereits Vorhandene ist unberührt geblieben. Demzufolge war der Benutzer authentifiziert und der Authorization Filter hat ihn einfach durchgelassen.

Lösung

Da der Geltungsbereich des Override-Attributs nur auf die Web API-Schicht beschränkt ist, wäre eine der vorstellbaren Lösungen, die Azure AD-Authentifizierung irgendwie in die Web API-Schicht zu verlagern. Im Web API 2 lässt sich dies durchaus machen und zwar, indem man die automatisch erfolgende Host-Authentifizierung ausschaltet und auf die bereits registrierte Azure AD-Authentifizierung von einem Authentication Filter aus (Web API-Schicht) verweist. Auf diese Weise bleibt die Authentifizierung in der Katana-Middleware weiterhin bestehen mit dem Unterschied, dass sich die Katana-Authentifizierung nun wie ein Web API-Authentifizierungsfilter verhält.

Folgender Codeschnipsel aus der Register-Methode aus der WebApiConfig-Datei registriert die Azure AD-Authentifizierung global als ein Web API-Authentifizierungsfilter, welcher jetzt vom Override-Attribut wie gewünscht außer Kraft gesetzt werden kann.

   1: config.SuppressDefaultHostAuthentication();

   2: config.Filters.Add(

   3:        new HostAuthenticationFilter(AuthenticationTypes.AzureActiveDirectory)

   4: );

Die HostAuthenticationFilter-Klasse ist ein Authentifizierungsfilter aus der Web API 2-Pipeline, der im Konstruktor als Parameter die bei der Katana-Middleware registrierte Authentifizierungsmethode entgegennimmt und bei Anfrage aufruft. In unserem Fall wird dieser Filter – da global registriert – immer aufgerufen, es sei denn, er wurde vom Override-Attribut überbrückt. Da dieser nun überbrückbar gemacht wurde, werden in der genannten POST-Methode keine Benutzer mehr durchgelassen, die durch Basic Authentication nicht identifiziert wurden.

Referenzen

[1] https://azure.microsoft.com/de-de/documentation/articles/active-directory-authentication-scenarios/

[2] https://azure.microsoft.com/de-de/documentation/articles/active-directory-devquickstarts-webapi-dotnet/

[3] https://msdn.microsoft.com/en-us/library/system.web.http.filters.iauthenticationfilter(v=vs.118).aspx

[4] https://msdn.microsoft.com/en-us/library/system.web.http.overrideauthenticationattribute(v=vs.118).aspx