Kategorie

Multi-Provider Authentisierung in Ruby on Rails

September 6th, 2011

Moderne Applikationen erlauben es ihren Benutzern über Single sign-on Dienste zu authentisieren.

In diesem Guide erkläre ich wie man mit Ruby on Rails eine Applikation erstellt die Multi-Provider Authentisierung über OAuth und OpenID Dienste wie die von Google, Facebook oder Twitter erlaubt.

Ich verwende dafür die Plugins Devise und OmniAuth.

Devise

Devise ist ein Authentisierungs-Plugin für Rails. In diesem Guide verwende ich es für

  • Benutzerkonten Verwaltung
  • Session Verwaltung
  • Als Schnittstelle zu OmniAuth

Ich definiere den devise gem in meinem Gemfile.

gem 'devise'

und installiere ihn mit bundler

bundle install

Danach führe ich den Generator aus um den Initializer zu erstellen.

rails generate devise:install

Den Initializer kann ich nun anschauen und bei Bedarf editieren:

vi config/initializers/devise.rb

Als nächstes füge ich devise dem User Model an (falls das Model noch nicht existiert wird es angelegt)

rails generate devise User

Damit wird auch ein migration File erstellt in welchem Helper Methoden für erweiterte Funktion von devise auskommentiert werden können.

  create_table(:users) do |t|
      t.database_authenticatable :null => false
      t.recoverable
      t.rememberable
      t.trackable
      # t.encryptable
      # t.confirmable
      # t.lockable :lock_strategy => :failed_attempts, :unlock_strategy => :both
      # t.token_authenticatable
      t.timestamps
  end

Für eine ausführliche Beschreibung der Optionen siehe: https://github.com/plataformatec/devise

Ich belasse die migration hier so wie sie generiert wurde und wende sie auf die Datenbank an.

rake db:migrate

OmniAuth

OmniAuth ist ein universell einsetzbarer Rubygem mit dem sich Multiprovider-Authentisierung realisieren lässt.

Erneut erweitere ich mein Gemfile. Diesmal um folgende Zeilen.

gem 'omniauth', :require => "omniauth/oauth"
gem 'oa-openid', :require => 'omniauth/openid'

Und führe erneut bundler aus.

bundle install

Facebook (OAuth)

Ich will meinen Benutzern die Möglichkeit geben sich mit ihrem Facebook Account an meiner Applikation zu authentisieren. Dazu muss ich zuerst meine Applikation bei Facebook registrieren um eine App ID zu erhalten.

Danach konfiguriere ich diese in config/initializers/devise.rb

config.omniauth :facebook, "APP_ID", "APP_SECRET"

Danach muss ich dem Model (in unserem Fall User) noch sagen, dass es über omniauth authentisierbar ist.

devise :omniauthable

Zudem braucht mein Model noch eine Funktion die definiert was beim Facebook login geschehen soll.
In diesem Beispiel wird anhand der von Facebook erhaltenen E-Mail Adresse ein bestehender Benutzer gesucht. Falls keiner existiert wird ein neuer Angelegt.

def self.find_for_facebook_oauth(access_token, signed_in_resource=nil)
  data = access_token['extra']['user_hash']
  if user = User.find_by_email(data["email"])
    user
  else 
    User.create!(:email => data["email"], :password => Devise.friendly_token[0,20])
  end
end

Jetzt muss meine Applikation nur noch die Callbacks verarbeiten können. Dazu ändere ich die devise_for Konfiguration in config/routes.rb zu:

devise_for :users, :controllers => { :omniauth_callbacks => "users/omniauth_callbacks" }

und erstelle den Callback Controller

# app/controllers/users/omniauth_callbacks_controller.rb
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
  def facebook
    @user = User.find_for_facebook_oauth(env["omniauth.auth"], current_user)
    if @user.persisted?
      flash[:notice] = I18n.t "devise.omniauth_callbacks.success", :kind => "Facebook"
      sign_in_and_redirect @user, :event => :authentication
    else
      session["devise.facebook_data"] = env["omniauth.auth"]
      redirect_to new_user_registration_url
    end
  end
end

vereine.ch

Ein Beispiel für die produktive Anwendung ist die von mir geschriebene Vereine Plattform vereine.ch

Diese erlaubt Login über konventionellen E-Mail Sign-Up, den Facebook OAuth Dienst und Google OpenID.