OAuth2 und ASP.NET

Benutzername und Passwort. Jeder Mensch hat unzählig viele davon. Und doch bräuchte er nur einmalig Zugangsdaten. Ähnlich wie bei einem Reisepass. Amazon lebt davon, das man mit einem Click, nicht nur bei Amazon, sondern bei unzähligen Partnern einkaufen kann. Keine neuen Credentials, keine Adresse, keine Kreditkarte eingeben.

Also spendieren wir unsere neuen Website eben keine eigen Benutzerdatenbank, sondern nehmen das her was die Leute ohnehin schon haben. Facebook, Google oder Microsoft Account. Diese Dienste Authentifizieren den Benutzer über ein Web Formular und geben einen Zeichenkette (Token) zurück mit dem zusätzliche Daten angefordert werden können oder Aktivitäten ausgelöst werden können. Es gibt zwar das Protokoll OAuth, aber die Umsetzungen sind sehr unterschiedlich. So muss bei Twitter oder Facebook der Entwickler vorher seine  App registrieren und bekommt dann Zugangsdaten, meist Appid und Secret genannt. Der weiter Zugriff auf Daten oder Dienste erfolgt dann über eine spezifische API bei Facebook Open Graph genannt. Da jeder Dienst eine eigen API hat, wurden Frameworks geschaffen um die Details zu abstrahieren. Für .NET erfreut sich das Open Source Framework Facebook.NET wachsender Beliebtheit. Um die Abhängigkeit von Facebook zu entkoppeln gibt es eine weitere Abstraktion OAuth für ASP.NET das sich das Membership System zu nutze macht .

Ein normales ASP.NET 4.5 Webforms Projekt enthält bereits Referenzen auf DotNetOpenAuth.OAuth und die notwendigen Design Templates im Verzeichnis Account.

Hinweis: der Projekt Owner hat zum 30.06 seine Funktion zurückgelegt und seither ruhen die Arbeiten. Die viel versprechende Version 5 liegt nur in einer Preview vor.

Außerdem findet sich im Verzeichnis app_start die Datei AuthConfig.vb mit der man die nötigen Authentifizierungsprovider einbauen kann.

   1:  Imports Microsoft.AspNet.Membership.OpenAuth
   2:   
   3:  Public Module AuthConfig
   4:      Sub RegisterOpenAuth()
   5:          ' See http://go.microsoft.com/fwlink/?LinkId=252803 for details on setting up this ASP.NET
   6:          ' application to support logging in via external services.
   7:   
   8:          'OpenAuth.AuthenticationClients.AddTwitter(
   9:          '    consumerKey:= "your Twitter consumer key",
  10:          '    consumerSecret:= "your Twitter consumer secret")
  11:   
  12:          'OpenAuth.AuthenticationClients.AddFacebook(
  13:          '    appId:= "your Facebook app id",
  14:          '    appSecret:= "your Facebook app secret")
  15:   
  16:          'OpenAuth.AuthenticationClients.AddMicrosoft(
  17:          '    clientId:= "your Microsoft account client id",
  18:          '    clientSecret:= "your Microsoft account client secret")
  19:   
  20:          'OpenAuth.AuthenticationClients.AddGoogle()
  21:      End Sub
  22:  End Module

Mal angenommen man nimmt alle vier Provider und besitzt auch schon die nötigen IDs und Secrets für die neue Anwendung, dann sollte der Login Dialog so aussehen.

image

Die Inhalte auf der rechten Seite des ASPX Login Dialogs kommen aus dem Usercontrol OpenAuthProviders.ascx. Dieses kann man natürlich auch in anderen Web Pages verwenden. Wenn die einfachen Buttons zu wenig hübsch sind oder man nicht genügend Platz hat( mobile Clients) muss man dieses Template anpassen und beim Anlegen des Providers über Extradata ein Link auf eine Bild mitliefern.

Das Usercontrol als Template für die oAuth Provider Auflistung.

   1:  <fieldset class="open-auth-providers">
   2:      <legend>Log in using another service</legend>
   3:      <asp:ListView runat="server" ID="providerDetails" 
ItemType="Microsoft.AspNet.Membership.OpenAuth.ProviderDetails"
   4:          SelectMethod="GetProviderNames" ViewStateMode="Disabled">
   5:          <ItemTemplate>
   6:             <button type="submit" name="provider" value="<%#: Item.ProviderName %>">
   7:              <img src="<%# Item.ExtraData("Icon")%>" alt="OAuth Login" /></button>
   8:          </ItemTemplate>

Im VB.NET Code wird dann beim registriere eines jeden Providers eine URI auf eine Image mitgegeben.

   1:      OpenAuth.AuthenticationClients.AddFacebook(
   2:            appId:="1944xxxxx0909",
   3:            appSecret:="a676fxxxxxxxxxx9d31b6ed1a",
   4:             extraData:=New With {.Icon = "../Images/facebook.png"}) 

Das Ergebnis in meiner eigenen Login Page vorher

image

und nachher (sofern der Benutzer bisher nicht an Facebook angemeldet ist)

image

 

Wer sich dann per Facebook einloggt wird aber dann in einem Facebook Dialog landen. Dies muss so sein um ersten die Sicherheit zu gewährleisten und zweitens für jedes Device den passenden Dialog zu haben.

image

Wer sich dann anmeldet, wird zurück auf die eigene Web Anwendung geleitet. Hier beginnt die Magic, weil dabei gleich eine ASP.NET Membership SQL Datenbank angelegt wird. Man erhält also mit User.Identity.Name den Benutzer ( bei Facebook die Mail Adresse). Wenn der Benutzer bei Facebook bereits angemeldet war, erscheint der Login Dialog (wahrscheinlich) gar nicht, sondern wird quasi automatisch angemeldet.

Wer noch mehr von Facebook erwartet, muss sich die Facebook SDK Library holen und in der registerexternallogin.aspx kurz vorm Redirect (Zeile 1-3) den Token krallen und in einer Session ablegen.

   1:  If authResult.ExtraData.Keys.Contains("accesstoken") Then
   2:              Session("mytoken") = authResult.ExtraData("accesstoken")
   3:  End If
   4:        
   5:   ' User has logged in with provider successfully
   6:   ' Check if user is already registered locally
   7:   If OpenAuth.Login(authResult.Provider, authResult.ProviderUserId, createPersistentCookie:=False) Then
   8:  ...
   9:   
  10:    End If

Schnell wechselt die Anforderung. Zuerst nur  Benutzeranmeldung, zu konsumieren des Facebook Dienstes. Mit dem ausgestellten Token können dann weitere Infos des Facebook Account gelesen werden.

   1:  'https://nuget.org/packages/Facebook/6.4.2
   2:  Dim fc = New FacebookClient(Session("mytoken"))
   3:   
   4:   Dim d = fc.[Get]("/me")

Obligatorisch wäre das “Hello World” Post in die eigene Timeline.

   1:  Protected Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
   2:          Dim fc = New FacebookClient(Session("mytoken"))
   3:          fc.Post("/me/feed", New With {.message = "hallo Welt"})
   4:   End Sub

Klappt nur leider nicht, wegen fehlender Rechte, hier wohl publish_stream. Könnte also im Browser ein

OAuthException - #200) (#200) The user hasn't authorized the application to perform this action

erscheinen. In APP Portal von Facebook kann man die nötigen Permissions einer Anwendung einstellen.

image

Der Benutzer muss das Recht allerdings der Anwendung noch erteilen, um diese erweiterte Funktion, zum Schreiben in die persönliche Timeline, auch zu bekommen. Dies wird mit einem Scope Parameter in der URL durchgeführt um ein höher privilegierten Token zu erhalten. DotnetOpenauth unterstützt das nicht direkt. Dazu ein anderer Blog Post.

Manöverkritik: Selten habe ich so viele Beispiele gesehen, die nicht funktionieren. Zudem sehen die Lösungswege total unterschiedlich aus. Ich würde mir hier ein Framework von Microsoft wünschen, das die ganze OAuth Geschichte samt den wichtigsten API Call’s kapselt. Muss auch kein Open Source sein Winking smile.

Weiters ist es einigermaßen Tricky mit einem Webserver zu arbeiten der auf localhost läuft. In der Kombi mit Internet Explorer hat die Facebook Authentifizierung nicht geklappt. Mit Chrome funktioniert es, aber bei Facebook müssen die lokalen Adressen eingestellt werden für die Weiterleitung.

Kommentare sind geschlossen