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
..Antworten:
Sie möchten CSRF nicht deaktivieren. Ich habe gelesen, dass die Leute denken, dass es aus irgendeinem Grund nicht für JSON-APIs gilt, aber dies ist ein Missverständnis. Um es aktiviert zu halten, möchten Sie einige Änderungen vornehmen:
Fügen Sie auf der Serverseite Ihrem Sitzungscontroller einen after_filter hinzu:
after_filter :set_csrf_header, only: [:new, :create] protected def set_csrf_header response.headers['X-CSRF-Token'] = form_authenticity_token end
Dadurch wird ein Token generiert, in Ihre Sitzung eingefügt und für ausgewählte Aktionen in den Antwortheader kopiert.
Auf der Clientseite (iOS) müssen Sie sicherstellen, dass zwei Dinge vorhanden sind.
Ihr Client muss alle Serverantworten nach diesem Header durchsuchen und bei der Weitergabe beibehalten.
... get ahold of response object // response may be a NSURLResponse object, so convert: NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse*)response; // grab token if present, make sure you have a config object to store it in NSString *token = [[httpResponse allHeaderFields] objectForKey:@"X-CSRF-Token"]; if (token) [yourConfig setCsrfToken:token];
Schließlich muss Ihr Client dieses Token zu allen von ihm gesendeten Nicht-GET-Anforderungen hinzufügen:
... get ahold of your request object if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"]) [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
Das letzte Puzzleteil besteht darin, zu verstehen, dass beim Anmelden zum Entwickeln zwei aufeinanderfolgende Sitzungen / CSRF-Token verwendet werden. Ein Anmeldefluss würde folgendermaßen aussehen:
GET /users/sign_in -> // new action is called, initial token is set // now send login form on callback: POST /users/sign_in <username, password> -> // create action called, token is reset // when login is successful, session and token are replaced // and you can send authenticated requests
quelle
auth_token
Anmeldung die Antwort abrufen und diese zusammen mit den nachfolgenden Anforderungen zur Authentifizierung als dieser Benutzer weitergeben muss?GET /users/sign_in
und erhält das CSRF-Token. 2) EsPOST /users/sign_in
wird das gerade empfangene CSRF-Token verwendet und erhält ein neues CSRF-Token. Dadurch wird auch ein Cookie in der iOS-App gespeichert und eine neue Sitzung erstellt. 3) Für alle zukünftigen Anforderungen authentifiziert sich die iOS-App mithilfe des Cookies und enthält das CSRF-Token zum Schutz bei Nicht-GET-Anforderungen. Habe ich recht?Ihr Beispiel scheint den Code aus dem Devise-Blog nachzuahmen - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6
Wie in diesem Beitrag erwähnt, machen Sie es ähnlich wie Option 1, die als unsichere Option bezeichnet wird. Ich denke, der Schlüssel ist, dass Sie das Authentifizierungstoken nicht jedes Mal zurücksetzen möchten, wenn der Benutzer gespeichert wird. Ich denke, das Token sollte explizit erstellt werden (durch eine Art TokenController in der API) und sollte regelmäßig ablaufen.
Sie werden feststellen, dass ich "Ich denke" sage, da (soweit ich das beurteilen kann) niemand mehr Informationen dazu hat.
quelle
Die 10 häufigsten Schwachstellen in Webanwendungen sind in den OWASP Top 10 dokumentiert . In dieser Frage wurde erwähnt, dass der CSRF-Schutz (Cross-Site Request Forgery) deaktiviert wurde und CSRF unter den OWASDP Top 10 ist . Kurz gesagt, CSRF wird von Angreifern verwendet, um Aktionen als authentifizierter Benutzer auszuführen. Das Deaktivieren des CSRF-Schutzes führt zu Sicherheitslücken in einer Anwendung mit hohem Risiko und untergräbt den Zweck eines sicheren Authentifizierungssystems. Es ist wahrscheinlich, dass der CSRF-Schutz fehlgeschlagen ist, weil der Client das CSRF-Synchronisationstoken nicht übergeben kann.
Lesen Sie die gesamten OWASP Top 10, wenn Sie dies nicht tun, ist dies äußerst gefährlich . Achten Sie genau auf die fehlerhafte Authentifizierung und Sitzungsverwaltung. Lesen Sie auch das Spickzettel zur Sitzungsverwaltung .
quelle