Meine Rails-App verwendet Devise zur Authentifizierung. Es verfügt über eine Schwester-iOS-App, und Benutzer können sich mit denselben Anmeldeinformationen, die sie für die Web-App verwenden, bei der iOS-App anmelden. Ich brauche also eine Art API für die Authentifizierung.
Viele ähnliche Fragen hier verweisen auf dieses Tutorial , aber es scheint veraltet zu sein, da das token_authenticatable
Modul inzwischen aus Devise entfernt wurde und einige der Zeilen Fehler auslösen. (Ich verwende Devise 3.2.2.) Ich habe versucht, mein eigenes basierend auf diesem Tutorial (und diesem ) zu rollen , aber ich bin nicht 100% sicher - ich habe das Gefühl, dass es etwas gibt, das ich habe missverstanden oder verpasst.
Zunächst habe ich gemäß den Anweisungen dieses Kerns ein authentication_token
Textattribut zu meiner users
Tabelle hinzugefügt und Folgendes zu user.rb
:
before_save :ensure_authentication_token
def ensure_authentication_token
if authentication_token.blank?
self.authentication_token = generate_authentication_token
end
end
private
def generate_authentication_token
loop do
token = Devise.friendly_token
break token unless User.find_by(authentication_token: token)
end
end
Dann habe ich folgende Controller:
api_controller.rb
class ApiController < ApplicationController
respond_to :json
skip_before_filter :authenticate_user!
protected
def user_params
params[:user].permit(:email, :password, :password_confirmation)
end
end
(Beachten Sie, dass mein application_controller
die Linie hat before_filter :authenticate_user!
.)
api / session_controller.rb
class Api::SessionsController < Devise::RegistrationsController
prepend_before_filter :require_no_authentication, :only => [:create ]
before_filter :ensure_params_exist
respond_to :json
skip_before_filter :verify_authenticity_token
def create
build_resource
resource = User.find_for_database_authentication(
email: params[:user][:email]
)
return invalid_login_attempt unless resource
if resource.valid_password?(params[:user][:password])
sign_in("user", resource)
render json: {
success: true,
auth_token: resource.authentication_token,
email: resource.email
}
return
end
invalid_login_attempt
end
def destroy
sign_out(resource_name)
end
protected
def ensure_params_exist
return unless params[:user].blank?
render json: {
success: false,
message: "missing user parameter"
}, status: 422
end
def invalid_login_attempt
warden.custom_failure!
render json: {
success: false,
message: "Error with your login or password"
}, status: 401
end
end
api / registrations_controller.rb
class Api::RegistrationsController < ApiController
skip_before_filter :verify_authenticity_token
def create
user = User.new(user_params)
if user.save
render(
json: Jbuilder.encode do |j|
j.success true
j.email user.email
j.auth_token user.authentication_token
end,
status: 201
)
return
else
warden.custom_failure!
render json: user.errors, status: 422
end
end
end
Und in config / route.rb :
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Ich bin ein bisschen überfordert und ich bin mir sicher, dass es hier etwas gibt, auf das mein zukünftiges Ich zurückblicken und zusammenzucken wird (das gibt es normalerweise). Einige zweifelhafte Teile:
Erstens werden Sie feststellen, dass Api::SessionsController
erbt von, Devise::RegistrationsController
während Api::RegistrationsController
erbt von ApiController
(Ich habe auch einige andere Controller, z. B. Api::EventsController < ApiController
die sich mit mehr Standard-REST-Dingen für meine anderen Modelle befassen und nicht viel Kontakt mit Devise haben.) Dies ist eine ziemlich hässliche Anordnung. Aber ich konnte keinen anderen Weg finden, um auf die Methoden zuzugreifen, die ich brauche Api::RegistrationsController
. Das Tutorial, auf das ich oben verlinkt habe, enthält die Zeile include Devise::Controllers::InternalHelpers
, aber dieses Modul scheint in neueren Versionen von Devise entfernt worden zu sein.
Zweitens habe ich den CSRF-Schutz mit der Leitung deaktiviert skip_before_filter :verify_authentication_token
. Ich habe meine Zweifel, ob dies eine gute Idee ist - ich sehe viele widersprüchliche oder schwer verständliche Ratschläge, ob JSON-APIs für CSRF-Angriffe anfällig sind -, aber das Hinzufügen dieser Zeile war der einzige Weg, um das verdammte Ding zum Laufen zu bringen.
Drittens möchte ich sicherstellen, dass ich verstehe, wie die Authentifizierung funktioniert, wenn sich ein Benutzer angemeldet hat. Angenommen, ich habe einen API-Aufruf, GET /api/friends
der eine Liste der Freunde des aktuellen Benutzers zurückgibt. Wie ich es verstehe, würde der iOS - App den Benutzer erhalten hat authentication_token
aus der Datenbank (die ein fester Wert für jeden Benutzer, die ich nie ändern ??), legen sie dann als param zusammen mit jeder Anfrage, zum Beispiel GET /api/friends?authentication_token=abcdefgh1234
, dann mein Api::FriendsController
tun könnte so etwas wie User.find_by(authentication_token: params[:authentication_token])
den current_user zu bekommen. Ist es wirklich so einfach oder fehlt mir etwas?
Also für alle, die es geschafft haben, bis zum Ende dieser Mammutfrage zu lesen, danke für Ihre Zeit! Zusammenfassen:
- Ist dieses Anmeldesystem sicher? Oder gibt es etwas, das ich übersehen oder missverstanden habe, z. B. bei CSRF-Angriffen?
- Verstehe ich, wie Anforderungen authentifiziert werden, wenn Benutzer angemeldet sind? (Siehe "drittens ..." oben.)
- Gibt es eine Möglichkeit, diesen Code zu bereinigen oder zu verbessern? Besonders das hässliche Design, einen Controller von
Devise::RegistrationsController
und die anderen von erben zu lassenApiController
.
Vielen Dank!
Api::SessionsController
erstreckt sich vonDevise::RegistrationsController
..