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!

questionAnswers(3)

yourAnswerToTheQuestion