OpenID in Rails einbinden

Das Integrieren von OpenID in eine Rails-Anwendung wird einem ziemlich leicht gemacht: Wer es weniger flexibel, dafür aber umso schneller fertig haben möchte, für den eignet sich das OpenID Consumer Plugin. Wer allerdings lieber selbst wissen möchte, was wie funktioniert, der kann das OpenID Login auch in wenigen Schritten selbst implementieren.

1. Installieren der Library

Für den Umgang mit OpenID (sowohl Consumer als auch Sever) gibt es bereits das Ruby-OpenID Gem. Da wir dessen Funktionalität nutzen wollen, muss es zunächst installiert werden. Einfach über die Konsole

$ sudo gem install ruby-openid -y

eingeben. Dadurch werden das Gem und die nötigen Abhängigkeiten (-y ist die Abkürzung für --include-dependencies) lokal installiert.

2. Der OpenID Controller

Um die nötigen Funktionen zu implementieren brauchen wir nur einen Controller und vier dazugehörige Actions. Um die OpenID-Funktionalität vom Rest der Anwendung zu trennen, empfiehlt es sich, einen neuen Controller zu generieren:

./script/generate controller Openid login begin complete openid_consumer

Der Befehl erzeugt uns den OpenidController mit den Actions login, begin, complete und openid_consumer, welche wir nacheinander implementieren werden. Bevor es damit allerdings losgehen kann, muss in Zeile 1 des neuen Controllers das in Schritt 1 installierte Gem eingebunden werden:

require_gem 'ruby-openid'

Damit stehen uns im Controller alle Funktionen des Gems bereit, welche wir für die Implementierung der Actions nutzen wollen. Der Controller dürfte bis dahin so aussehen:

require_gem 'ruby-openid'
class OpenidController < ApplicationController

  def login
  end

  def begin
  end

  def complete
  end

  def openid_consumer
  end

end

3. openid_consumer

Wir fangen mit der Action openid_consumer an und markieren sie als protected, da sie nur Controller-intern verwendet werden wird und daher kein Zugriff über die URL erlaubt werden soll.

  protected
    def openid_consumer
      @openid_consumer ||= OpenID::Consumer.new(session,
        OpenID::FilesystemStore.new("#{RAILS_ROOT}/tmp/openid"))
    end

Der Einzeiler erstellt eine Openid-Consumer Instanz: Das erste Argument muss ein Hash-Objekt sein, welches die Sessiondaten speichert (in Rails also session) und der zweite Parameter legt fest, wo die Daten des Verifizierungsprozesses abgelegt werden sollen. In diesem Fall nehmen wir das Dateisystem - es ist allerdings auch möglich, die Datenbank zu nutzen (das in der Einleitung erwähnte Plugin macht es über Active Record).

4. login

In der Action login braucht nichts weiter passieren, es geht lediglich darum, den View mit dem Formular bereitzustellen, welches im einfachsten Falle irgendwie so aussieht:

<% form_tag url_for(:action => :begin) do %>
  <%= text_field_tag 'openid_url'  %>
  <%= submit_tag "mit OpenID einloggen" %>
<% end -%>

Laut Konvention sollte man immer das OpenID-Logo als Hintergrundbild für das Loginfeld einbinden, was sich mit folgendem CSS leicht tun lässt:

#openid_url {
  background: url(/images/login-bg.gif) no-repeat #FFF 5px;
  padding-left: 25px;
}

5. begin

Nächster Schritt ist die Action begin, welche die aus dem Formular abgeschickten Anfragen annimmt und verarbeitet.

  def begin
    openid = params[:openid_url]
    response = openid_consumer.begin openid
    if response.status == OpenID::SUCCESS
      # Daten anfordern - siehe Spec:
      # http://openid.net/specs/openid-simple-registration-extension-1_0.html
      response.add_extension_arg('sreg','required','nickname,email')
      response.add_extension_arg('sreg','optional','fullname')
      # Request senden - erster Parameter = Trusted Site,
      # zweiter Parameter = anschließende Weiterleitung
      redirect_to response.redirect_url(home_url, openid_complete_url)
    else
      flash[:error] = "Der OpenID-Server #{openid} war nicht erreichbar."
      redirect_to url_for(:action => :login)
    end
  end

Im Falle eines erfolgreichen Response des vom User angegebenen OpenID-Servers wird ein Request mit der Anforderung der Userdaten gesendet. In diesem Fall werden die Daten nickname und email zwingend angefordert, fullname ist optional. Darüber hinaus gibt es noch weitere Daten, welche man über das OpenID-Login beziehen kann, eine genaue Auflistung findet sich in der Spezifikation des Response-Formats.

Der Request zum Abschließen der Verifikation wird mit zwei Parametern an den OpenID-Server geschickt: Beim ersten Parameter handelt es sich um die URL der Website, welcher der User die Daten zur Verfügung stellt (also die Basis-URL unserer Anwendung - in diesem Fall über benannte Routes die home_url. Der zweite Parameter legt fest, wo die anschließende Verarbeitung des Request stattfinden soll, also in unserer complete Action (wäre ohne benannte Routes url_for(:controller =&gt; :openid, :action =&gt; :complete)).

6. complete

Hier nun also der letzte Schritt, in welchem die Antwort mit der OpenID-Verifizierung verarbeitet wird.

  def complete
    response = openid_consumer.complete params
    openid = response.identity_url
    if response.status == OpenID::SUCCESS
      # Hier muss das Login implementiert werden,
      # also Session befüllen oder sonstige Aktionen.
      # Die Daten stehen als
      # params["openid.sreg.nickname"]
      # etc. zur Verfügung
      end
    else
      flash[:error] = "Das Login mit der OpenID #{openid} war nicht erfolgreich."
      redirect_to login_url
    end
  end

Wie das letztendliche Login aussieht (Eintragen in Session, Erstellen eines Benutzerkontos oder ähnliches), hängt von der Anwendung ab und muss im auskommentierten Teil selbst implementiert werden. Die vom OpenID-Server gelieferten Daten des Users stehen über params["openid.sreg.nickname"] bereit.

Anmerkungen

Beim Schreiben des Tutorials habe ich mich an Dan Webbs No Shit Guide To Supporting OpenID In Your Applications orientiert. Mittlerweile gibt es auch vom Rails Core Team das OpenID Authentication Plugin, welches auch als Wrapper für die Funktionen des Ruby-OpenID Gems dient.

iOS app for GitHub

iOctocat

ist GitHub für die Hosentasche - deine Projekte und das was dort passiert immer dabei mit deinem iPhone und iPod Touch.
Die App ist