简体   繁体   中英

Rails Admin not authenticating with Cancancan or Devise

I tried this in the config:

  ### Popular gems integration
  config.authenticate_with do
    warden.authenticate! scope: :user
  end
  config.current_user_method(&:current_user)

here, accessing /admin gets me stuck in a login loop. I login then get redirected to the login page.

Previously I had tried

class Ability
  include CanCan::Ability

  def initialize(user)
    # Define abilities for the passed in user here. For example:
    #
      user ||= User.new # guest user (not logged in)
      if user.admin?

with the CanCanCan authentication in rails admin config. This resulted in user always being nil. Even when I put in

config.current_user_method(&:current_user)

How do I fix this to authenticate was an admin?

EDIT: in sessions controller

      if user.admin
        redirect_to rails_admin_url
      else
        render json: user
      end

and I get stuck in the redirect back to signin.

Routes:

Rails.application.routes.draw do
  mount RailsAdmin::Engine => '/admin', as: 'rails_admin'
  devise_for :users, controllers: { sessions: :sessions },
                      path_names: { sign_in: :login }
... then many resources lines

EDIT:

Sessions Controller:

class SessionsController < Devise::SessionsController
  protect_from_forgery with: :null_session

  def create
    user = User.find_by_email(sign_in_params[:email])
    puts sign_in_params
    if user && user.valid_password?(sign_in_params[:password])
      user.generate_auth_token
      user.save
      if user.admin
        redirect_to rails_admin_url
      else
        render json: user
      end
    else
      render json: { errors: { 'email or password' => ['is invalid'] } }, status: :unprocessable_entity
    end
  end
end

Rails admin config, trying this:

  ### Popular gems integration
  config.authenticate_with do
    redirect_to merchants_path unless current_user.admin?
  end
  config.current_user_method(&:current_user)

In ApplicationController:

def current_user
    @current_user ||= User.find(session[:user_id]) if session[:user_id]
  end
  helper_method :current_user

Tried this, current_user is nil.

You might need to define the after_sign_in_path_for method on the ApplicationController

By default, it first tries to find a valid resource_return_to key in the session, then it fallbacks to resource_root_path, otherwise it uses the root path. For a user scope, you can define the default url in the following way:

get '/users' => 'users#index', as: :user_root # creates user_root_path

namespace :user do
  root 'users#index' # creates user_root_path
end

If the resource root path is not defined, root_path is used. However, if this default is not enough, you can customize it, for example:

def after_sign_in_path_for(resource)
  stored_location_for(resource) ||
    if resource.is_a?(User) && resource.can_publish?
      publisher_url
    else
      super
    end
end

THE CONTROLLER LOGIC FOR REDIRECT

The solution from Guillermo Siliceo Trueba will not work with your Users::SessionsController#create action as you are not calling in the parent class create action through the keyword super .

The method create from the class Devise::SessionsController will run respond_with in the last line using as location the value returned from after_sign_in_path_for(resource)

class Devise::SessionsController < DeviseController
  # POST /resource/sign_in
  def create
    self.resource = warden.authenticate!(auth_options)
    set_flash_message!(:notice, :signed_in)
    sign_in(resource_name, resource)
    yield resource if block_given?
    respond_with resource, location: after_sign_in_path_for(resource)
  end
end

I am using this solution in my Users::SessionsController to handle html and json requests and you can implement the same.

If the controller receives the request with format .json , the code between format.json do .. end is executed, if the request arrives with format.html the parent method is called (I cover all your scenarios).

Rails router identifies the request format from the .json or .html appended at the end of the url (for example GET https://localhost:3000/users.json )

class Users::SessionsController < Devise::SessionsController
  def create
    respond_to do |format|
      format.json do 
        self.resource = warden.authenticate!(scope: resource_name, recall: "#{controller_path}#new")
        render status: 200, json: resource
      end
      format.html do 
        super
      end
    end
  end
end

The html request will be routed to the super create action . You just need to override the method def after_sign_in_path_for(resource) from the parent as I am doing in my ApplicationController .

if resource.admin then just return rails_admin_url from this method and skip the other lines, otherwise follow the normal behavior and call super#after_sign_in_path_for(resource)

def after_sign_in_path_for(resource)
  return rails_admin_url if resource.admin
  super
end

THE ERROR MESSAGES FOR AUTHENTICATION

warned.authenticate! will save inside self.resource.errors the error messages. You just need to display the errors on your device using json_response[:error] and manipulating the response in your frontend.

I am rendering them in the SignInScreen Component

在此处输入图片说明

THE TOKEN AUTHENTICATION

user.generate_auth_token
user.save

I use the simple_token_authentication for devise which also allows you to regenerate the token every time the user signs in .

You just install the gem and add acts_as_token_authenticatable inside your user model .

class User < ApplicationRecord
   acts_as_token_authenticable
end

You pass headers X-User-Email and X-User-Token with the corresponding values as explained in their guide .

Other alternatives to simple_token_authentication

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM