homeASCIIcasts

270: Authentifizierung in Rails 3.1 

(view original Railscast)

Other translations: En Ja Es Fr

Other formats:

Written by Stephan Lukas

Rails 3.1 bringt neue Features zur Authentifizierung mit sich. In dieser Folge werden wir euch einige dieser Funktionen vorstellen. Wir verwenden hierfür eine einfache Anwendung. Die Anwendung besteht aus einer Seite, welche momentan für jeden zugänglich ist, der die zugehörige URL kennt.

Die Seite, die wir schützen wollen.

HTTP Basic Authentication

Wir wollen den Zugang zu der Seite einschränken, so dass sie nur von berechtigten Benutzern betrachtet werden kann. Es ist also notwendig, dass wir einen Nutzer authentifizieren können und die einfachste Möglichkeit ist hier die HTTP Basic Authentication. Rails 3.1 bietet dafür eine neue Variante. Das einzige was wir zu tun haben ist den Controller der Seite zu ergänzen. Wir fügen einen Aufruf der Methode http_basic_authenticate_with mit den Optionen :name und :password hinzu.

/app/controllers/secret_controller.rb

class SecretController < ApplicationController
  http_basic_authenticate_with :name => "frodo", :password => "thering"
  def index
  end
end

Wenn wir die Authentifizierung auf bestimmte Actions beschränken wollen, können wir die Optionen :only oder :except verwenden. Natürlich würden wir in einer echten Anwendung den Benutzernamen und das Passwort nicht als Klartext in den Quellcode schreiben, sondern diese in eine Konfigurationsdatei auslagern. Aber für diese Beispielanwendung können wir sie hier belassen.

Wenn wir jetzt unsere Seite aufrufen, begrüßt uns ein Anmeldefenster, welches nur die eben über http_basic_authenticate_with vorgegebenen Anmeldedaten akzeptiert.

Die Seite fordert nun gültige Anmeldedaten.

HTTP Basic ist zwar recht schlicht gehalten, dafür bietet es aber einen schnellen und einfachen Weg Teile einer Anwendung zu schützen und mit Rails 3.1 ist es dazu sogar noch leichter einzusetzen.

secure_password

Manchmal benötigen wir aber ein etwas umfangreicheres Autorisierungssystem, das auch mit mehreren Nutzern umgehen kann. Rails 3.1 hilft uns hier mit secure_password weiter.

In Folge 250 [watch, read] hatten wir eine Authentifizierung von Grund auf selbst programmiert. Rails 3.1 macht dies jetzt ein wenig einfacher. Um das zu demonstrieren, werden wir jetzt die HTTP Basic Authentifizierung durch unsere eigene ersetzen.

Als erstes generieren wir für unsere Nutzer ein Model User mit einer Emailadresse und einem Passwort.

$ rails g model user email:string password_digest:string

Anschließend migrieren wir die Datenbank und erzeugen so die Nutzertabelle.

$ rake db:migrate

Wichtig hierbei ist, dass wir das Feld zur Speicherung des Passworts password_digest nennen. Als nächstes müssen wir im Model User ein Aufruf von has_secure_password hinzufügen.

/app/models/user.rb

class User < ActiveRecord::Base
  has_secure_password
end

Hierdurch werden Methoden zum Setzen und Überprüfen des eingegebenen Passworts, Validatoren für das Passwort und die Passwortbestätigung (confirmation), sowie weitere Authentifizierungsfunktionalität hinzugefügt. Das Feld password_digest, dass wir vorhin angelegt haben, dient zur Speicherung des Passworts als Hash.

Standardmäßig existiert noch kein Validator validates_presence_of der sicherstellt, dass beim Anlegen eines Nutzers überhaupt ein Passwort eingegeben wurde. Also erstellen wir uns als nächstes einen solchen.

/app/models/user.rb

class User < ActiveRecord::Base
  has_secure_password
  validates_presence_of :password, :on => :create
end

In der Regel würden wir auch die Emailadresse validieren. Das sparen wir uns aber hier.

Damit sich Nutzer auch Accounts erstellen können, erzeugen wir als nächsten den Nutzercontroller.

$ rails g controller users

Der Quelltext des Controllers enthält nichts besonderes:

/app/controllers/users_controller.rb

class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(params[:user])
    if @user.save
      redirect_to root_url, :notice => "Signed up!"
    else
      render "new"
    end
  end
end

Die neue View besteht aus einem Formular, mit dem sich die Nutzer registrieren können.

/app/views/users/new.html.erb

<h1>Sign Up</h1>

<%= form_for @user do |f| %>
  <% if @user.errors.any? %>
    <div class="error_messages">
      <h2>Form is invalid</h2>
      <ul>
        <% for message in @user.errors.full_messages %>
          <li><%= message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>
  <div class="field">
    <%= f.label :email %>
    <%= f.text_field :email %>
  </div>
  <div class="field">
    <%= f.label :password %>
    <%= f.password_field :password %>
  </div>
  <div class="field">
    <%= f.label :password_confirmation %>
    <%= f.password_field :password_confirmation %>
  </div>
  <div class="actions"><%= f.submit %></div>
<% end %>

Wenn wir nun versuchen, uns mit zwei nicht identischen Passwörtern zu registrieren, sehen wir die Validatoren in Aktion, welche durch den Aufruf von has_secure_password bereitgestellt wurden.

has_secure_password stellt uns automatisch einen Confirmationvalidatior bereit.

Geben wir beide Passwörter korrekt ein, werden wir erfolgreich registriert.

Die Nutzer können sich nun zwar registrieren, aber noch nicht anmelden. Also erzeugen wir als nächstes ein Anmeldeformular zur Eingabe von Emailadresse und Passwort.

/app/views/sessions/new.html.erb

<h1>Log in</h1>

<%= form_tag sessions_path do %>
  <div class="field">
    <%= label_tag :email %>
    <%= text_field_tag :email, params[:email] %>
  </div>
  <div class="field">
    <%= label_tag :password %>
    <%= password_field_tag :password %>
  </div>
  <div class="actions"><%= submit_tag "Log in" %></div>
<% end %>

Da wir kein Model bearbeiten, verwenden wir anstelle des form_for den form_tag Helfer. Das Formular wird an sessions_path gesendet. Also benötigen wir als nächstes einen Controller für Sessions.

$ rails g controller sessions

Damit Nutzer sich an- und abmelden können, besitzt der Controller new, create und destroy Actions.

/app/controllers/sessions_controller.rb

class SessionsController < ApplicationController
  def new
  end

  def create
    if # authenticated?
      session[:user_id] = user.id
      redirect_to root_url, :notice => "Logged in!"
    else
      flash.now.alert = "Invalid email or password"
      render "new"
    end
  end

  def destroy
    session[:user_id] = nil
    redirect_to root_url, :notice => "Logged out!"
  end
end

Die create Action ist noch nicht ganz vollständig. Wir müssen noch prüfen, ob der Nutzer das richtige Passwort eingegeben hat. Hier hilft uns die neue secure_password Funktionalität aus Rails 3.1.

Als erstes suchen wir uns den zur eingegebenen Emailadresse passenden Nutzer. Dank has_secure_password verfügen Nutzer nun über die Methode authenticate. Ihr übergeben wir das Passwort und lassen es gegen das verschlüsselte Passwort in der Datenbank abgleichen. Konnte erst gar kein passender Nutzer gefunden werden, liefert find_by_email nil zurück. Deshalb prüfen wir erst einmal, ob der Nutzer überhaupt existiert, bevor wir ihn authentifizieren.

/app/controllers/sessions_controller.rb

def create
  user = User.find_by_email(params[:email])
  if user && user.authenticate(params[:password])
    session[:user_id] = user.id
    redirect_to root_url, :notice => "Logged in!"
  else
    flash.now.alert = "Invalid email or password"
    render "new"
  end
end

Mehr ist nicht notwendig, um einen Nutzer über secure_password zu authentifizieren. Zum Testen versuchen wir uns einfach anzumelden. Geben wir ein falsches Passwort ein, werden wir auch nicht authentifiziert.

An error is thrown if the username or password are incorrect.

Geben wir hingegen gültige Daten ein, werden wir angemeldet und zur Startseite weitergeleitet.

Um überall in unserer Anwendung Zugriff auf den aktuellen Nutzer zu haben, erstellen wir die Methode current_user im ApplicationController und machen aus ihr eine Helfermethode, sodass sie in den Views verfügbar ist. Die Methode holt sich den aktuell angemeldeten Nutzer aus der Session.

/app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
  protect_from_forgery
  
  private
  
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  
  helper_method :current_user
end

Wirklich schön an dieser Form der Authentifizierung ist das einfache User Model. Es besteht gerade einmal aus zwei Zeilen Quelltext.

/app/models/user.rb

class User < ActiveRecord::Base
  has_secure_password
  validates_presence_of :password, :on => :create
end

Unsere Version aus Folge 250 hatte im Vergleich ein deutlich komplexeres User Model. Unabhängig davon ist es noch sinnvoll durch die Zeile attr_accessible im Nutzermodel den möglichen Zugriff auf email, password und password_confirmation zu beschränken.

/app/models/user.rb

class User < ActiveRecord::Base
  attr_accessible :email, :password, :password_confirmation
  has_secure_password
  validates_presence_of :password, :on => :create
end

HTTPS hinzufügen

Wenn wir schon eine Authentifizierung in unserer Anwendung anbieten, wollen wir natürlich die Anmeldedaten der Nutzer nicht ungeschützt übertragen. Es ist in diesem Fall eine gute Idee SSL zu verwenden und auf HTTPS zu wechseln. Vor Rails 3.1 musste das manuell oder über ein Plugin erledigt werden. Heute gibt es einen deutlich einfacheren Weg.

Um einen Controller auf HTTPS-Zugriff zu beschränken, brauchen wir nur die Klassenmethode force_ssl aufzurufen. Wollen wir nur bestimmte Actions schützen, verwenden wir die von before_filter bekannten Optionen :only oder :except.

/app/controllers/application_controller.rb

class ApplicationController < ActionController::Base
  protect_from_forgery
  force_ssl
  private
  
  def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  
  helper_method :current_user
end

Die Methode force_ssl erzwingt nur in der Test- oder Produktionsumgebung HTTPS. Wenn wir jetzt unseren Anwendungsserver in der Produktionsumgebung neustarten und die Anmeldeseite aufrufen, werden wir zur SSL-Variante weitergeleitet.

Die Seite erfordert nun SSL.

In diesem Fall sehen wir eine Fehlermeldung, da unser Server kein HTTPS unterstützt. Wäre dies der Fall, würden wir hier die sichere Version unserer Seite sehen.

Das war es auch schon wieder mit unserer Folge zum Thema Authentifizierung mit Rails 3.1. Die ganzen vorgestellten Erweiterungen machen es uns sehr viel einfacher eine Authentifizierung zu unseren Railsanwendungen hinzuzufügen.