IT TIP

이 Rails JSON 인증 API (Devise 사용)는 안전합니까?

itqueen 2020. 11. 5. 19:59
반응형

이 Rails JSON 인증 API (Devise 사용)는 안전합니까?


내 Rails 앱은 인증을 위해 Devise를 사용합니다. 자매 iOS 앱이 있으며 사용자는 웹 앱에 사용하는 것과 동일한 자격 증명을 사용하여 iOS 앱에 로그인 할 수 있습니다. 그래서 인증을 위해 일종의 API가 필요합니다.

여기에있는 많은 유사한 질문 이이 튜토리얼을 가리 키지 token_authenticatable모듈이 Devise에서 제거되고 일부 줄에서 오류가 발생 하기 때문에 구식 인 것 같습니다 . (저는 Devise 3.2.2를 사용하고 있습니다.) 저는 그 튜토리얼 ( 그리고이 튜토리얼)을 기반으로 제 자신을 굴려 보려고했지만 100 % 자신이 없습니다. 오해 또는 놓친.

첫째, 이 요점 의 조언에 authentication_token따라 내 users테이블에 텍스트 속성을 추가 하고 다음을에 추가했습니다 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

그런 다음 다음 컨트롤러가 있습니다.

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

( application_controller내에 줄이 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

그리고 config / routes.rb에서 :

  namespace :api, defaults: { format: "json" } do
    devise_for :users
  end

나는 내 깊이에서 약간 벗어 났고 내 미래의 자아가 되돌아보고 움찔 할 무언가가 여기에 있다고 확신합니다 (보통 있습니다). 일부 불완전한 부분 :

Firstly, you'll notice that Api::SessionsController inherits from Devise::RegistrationsController whereas Api::RegistrationsController inherits from ApiController (I also have some other controllers such as Api::EventsController < ApiController which deal with more standard REST stuff for my other models and don't have much contact with Devise.) This is a pretty ugly arrangement, but I couldn't figure out another way of getting access the methods I need in Api::RegistrationsController. The tutorial I linked to above has the line include Devise::Controllers::InternalHelpers, but this module seems to have been removed in more recent versions of Devise.

Secondly, I've disabled CSRF protection with the line skip_before_filter :verify_authentication_token. I have my doubts about whether this is a good idea - I see a lot of conflicting or hard to understand advice about whether JSON APIs are vulnerable to CSRF attacks - but adding that line was the only way I could get the damn thing to work.

Thirdly, I want to make sure I understand how authentication works once a user has signed in. Say I have an API call GET /api/friends which returns a list of the current user's friends. As I understand it, the iOS app would have to get the user's authentication_token from the database (which is a fixed value for each user that never changes??), then submit it as a param along with every request, e.g. GET /api/friends?authentication_token=abcdefgh1234, then my Api::FriendsController could do something like User.find_by(authentication_token: params[:authentication_token]) to get the current_user. Is it really this simple, or am I missing something?

So for anyone who's managed to read all the way to the end of this mammoth question, thanks for your time! To summarise:

  1. Is this login system secure? Or is there something I've overlooked or misunderstood, e.g. when it comes to CSRF attacks?
  2. Is my understanding of how to authenticate requests once users are signed in correct? (See "thirdly..." above.)
  3. Is there any way this code can be cleaned up or made nicer? Particularly the ugly design of having one controller inherit from Devise::RegistrationsController and the others from ApiController.

Thanks!


You don't want to disable CSRF, I have read that people think it doesn't apply to JSON APIs for some reason, but this is a misunderstanding. To keep it enabled, you want to make a few changes:

  • on there server side add a after_filter to your sessions controller:

    after_filter :set_csrf_header, only: [:new, :create]
    
    protected
    
    def set_csrf_header
       response.headers['X-CSRF-Token'] = form_authenticity_token
    end
    

    This will generate a token, put it in your session and copy it in the response header for selected actions.

  • client side (iOS) you need to make sure two things are in place.

    • your client needs to scan all server responses for this header and retain it when it is passed along.

      ... 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];
      
    • finally, your client needs to add this token to all 'non GET' requests it sends out:

      ... get ahold of your request object
      if (yourConfig.csrfToken && ![request.httpMethod isEqualToString:@"GET"])
        [request setValue:yourConfig.csrfToken forHTTPHeaderField:@"X-CSRF-Token"];
      

Final piece of the puzzle is to understand that when logging in to devise, two subsequent sessions/csrf tokens are being used. A login flow would look like this:

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

Your example seems to mimic the code from the Devise blog - https://gist.github.com/josevalim/fb706b1e933ef01e4fb6

As mentioned in that post, you are doing it similar to option 1, which they say is the insecure option. I think the key is that you don't want to simply reset the authentication token every time the user is saved. I think the token should be created explicitly (by some kind of TokenController in the API) and should expire periodically.

You'll notice I say 'I think' since (as far as I can tell) nobody has any more information on this.


The top 10 most common vulnerablites in web applications are documented in the OWASP Top 10. This question mentioned that Cross-Site Request Forgery(CSRF) protection was disabled, and CSRF is on the OWASDP Top 10. In short, CSRF is used by attackers to perform actions as an authenticated user. Disabling CSRF protection will lead to high risk vulnerabilities in an application, and undermines the purpose of having a secure authentication system. Its likely that the CSRF protection was failing, because the client is failing to pass the CSRF synchronization token.

Read the entire OWASP top 10, failing to do so is extremely hazardous. Pay close attention to Broken Authentication and Session Management, also check out the Session Management Cheat Sheet.

참고URL : https://stackoverflow.com/questions/20745843/is-this-rails-json-authentication-api-using-devise-secure

반응형