Czy ten interfejs API uwierzytelniania Rails JSON (przy użyciu Devise) jest bezpieczny?
Aplikacja My Rails używa Devise do uwierzytelniania. Ma siostrzaną aplikację na iOS, a użytkownicy mogą logować się do aplikacji iOS przy użyciu tych samych poświadczeń, których używają w aplikacji internetowej. Potrzebuję więc pewnego rodzaju API do uwierzytelniania.
Wiele podobnych pytań tutajten samouczek, ale wydaje się, że jest nieaktualny, podobnie jaktoken_authenticatable
moduł został usunięty z Devise, a niektóre linie rzucają błędy. (Używam Devise 3.2.2.) Próbowałem rzucić własne na podstawie tego samouczka (iten), ale nie jestem w tym w 100% pewny - czuję, że może być coś, co źle zrozumiałem lub przegapiłem.
Po pierwsze, zgodnie z zaleceniamiten sens, Dodałemauthentication_token
atrybut tekstowy do mojegousers
tabela, a następnie douser.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
Następnie mam następujące kontrolery:
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
(Zauważ, że mójapplication_controller
ma liniębefore_filter :authenticate_user!
.)
api / sessions_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
I wconfig / routes.rb:
namespace :api, defaults: { format: "json" } do
devise_for :users
end
Jestem trochę poza moją głębią i jestem pewien, że coś tu jest, że moje przyszłe ja będzie patrzeć wstecz i kulić się (zwykle jest). Niektóre dziwaczne części:
po pierwsze, zauważysz toApi::SessionsController
dziedziczy zDevise::RegistrationsController
natomiastApi::RegistrationsController
dziedziczy zApiController
(Mam też kilka innych kontrolerów, takich jakApi::EventsController < ApiController
które zajmują się bardziej standardowymi materiałami REST dla moich innych modeli i nie mają zbyt dużego kontaktu z Devise.) To jest dość brzydka aranżacja, ale nie mogłem znaleźć innego sposobu na uzyskanie dostępu do metod, których potrzebuję wApi::RegistrationsController
. Samouczek I powiązany z powyższym ma linięinclude Devise::Controllers::InternalHelpers
, ale ten moduł wydaje się być usunięty w nowszych wersjach Devise.
Po drugie, Wyłączyłem ochronę CSRF liniąskip_before_filter :verify_authentication_token
. Mam wątpliwości, czy to dobry pomysł - widzę wielesprzeczny lubciężko zrozumieć porady dotyczące tego, czy interfejsy API JSON są podatne na ataki CSRF - ale dodanie tej linii było jedynym sposobem, w jaki mogłem uruchomić to cholerstwo.
Po trzecie, Chcę się upewnić, że rozumiem, jak działa uwierzytelnianie po zalogowaniu się użytkownika. Powiedz, że mam połączenie z interfejsem APIGET /api/friends
która zwraca listę przyjaciół bieżącego użytkownika. Jak rozumiem, aplikacja iOS musiałaby uzyskać użytkownikaauthentication_token
z bazy danych (która jest stałą wartością dla każdego użytkownika, który nigdy się nie zmienia?), a następnie prześlij ją jako parametr wraz z każdym żądaniem, np.GET /api/friends?authentication_token=abcdefgh1234
, a potem mójApi::FriendsController
mógł zrobić coś takiegoUser.find_by(authentication_token: params[:authentication_token])
aby uzyskać bieżący użytkownik. Czy to naprawdę takie proste, czy coś mi brakuje?
Tak więc dla każdego, kto zdołał przeczytać aż do końca tego gigantycznego pytania, dziękuję za poświęcony czas! Podsumować:
Czy ten system logowania jest bezpieczny? Czy jest coś, co przeoczyłem lub źle zrozumiałem, np. jeśli chodzi o ataki CSRF?Czy rozumiem, jak uwierzytelniać żądania po poprawnym zalogowaniu się użytkowników? (Patrz „trzeci ...” powyżej.)Czy jest jakiś sposób, w jaki ten kod można oczyścić lub uczynić przyjemniejszym? Szczególnie brzydki projekt polegający na dziedziczeniu jednego kontroleraDevise::RegistrationsController
a inni zApiController
.Dzięki!