rubyajaxruby-on-rails-4devisewarden

Devise - Sign In with Ajax


Is here any possibility to modify devise SessionsController for ajax communication?


Solution

  • 1. Generate Devise controllers so we can modify it

    rails g devise:controllers
    

    enter image description here

    Now we have all controllers in the app/controllers/[model] directory

    2. Edit routes.rb

    Let's set Devise to use our modified SessionsController

    First add this code (of course change :users to your devise model) into config/routes.rb

    devise_for :users, controllers: {
      sessions: 'users/sessions'
    }
    

    3. Modify sessions_controller.rb

    enter image description here

    Find the create method and change it to

    def create
      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
        return render nothing: true
      end
    
      invalid_login_attempt
     end
    

    Create new method after protected

    def invalid_login_attempt
      set_flash_message(:alert, :invalid)
      render json: flash[:alert], status: 401
    end
    

    4. devise.rb

    Insert this into config/initializers/devise.rb

    config.http_authenticatable_on_xhr = false
    config.navigational_formats = ["*/*", :html, :json]
    

    5. Invalid email or password message

    Insert a new message into config/locales/devise.en.yml under the sessions

    invalid: "Invalid email or password."
    

    sessions

    6. View

    = form_for resource, url: session_path(:user), remote: true do |f|
      = f.text_field :email
      = f.password_field :password
      = f.label :remember_me do
        Remember me
        = f.check_box :remember_me
      = f.submit value: 'Sign in'
    
    :javascript
      $(document).ready(function() {
        //form id
        $('#new_user')
        .bind('ajax:success', function(evt, data, status, xhr) {
          //function called on status: 200 (for ex.)
          console.log('success');
        })
        .bind("ajax:error", function(evt, xhr, status, error) {
          //function called on status: 401 or 500 (for ex.)
          console.log(xhr.responseText);
        });
      });
    

    Important thing remote: true

    The reason why I am using status 200 or 401 unlike {status: 'true'} is less data size, so it is much faster and cleaner.

    Explanation

    On signing in, you get these data in params

    action: "create"
    commit: "Sign in"
    controller: "users/sessions"
    user: {
      email: "test@test.cz"
      password: "123"
      remember_me: "0"
    }
    utf8: "✓"
    

    Before signing, you need to authorize the user.

    resource = User.find_for_database_authentication(email: params[:user][:email])
    

    User.find_for_database_authentication

    If user is found, resource will be filled with something like

    created_at: "2015-05-29T12:48:04.000Z"
    email: "test@test.cz"
    id: 1
    updated_at: "2015-06-13T19:56:54.000Z"
    

    Otherwise will be

    null
    

    If the user is authenticated, we are about to validate his password

    if resource.valid_password?(params[:user][:password])
    

    And finally sign in

    sign_in :user, resource
    

    Sources

    SessionsController

    Helped me Andreas Lyngstad