nsarno / knock

Seamless JWT authentication for Rails API

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Usage with Devise

kevinhughes27 opened this issue · comments

Hello! First I want to thank you for this gem. It's been great to work with so far.

I'm using it in a larger project that has a few clients including a standard Rails web client. The project already uses Devise and now needs JWT for a new frontend client. I ran into some issues because Devise adds a current_user method to every controller. When knock tries to authenticate it checks if this method already exists and then just uses the one provided by Devise:

https://github.com/nsarno/knock/blob/master/lib/knock/authenticable.rb#L44

To work around this I just re-defined my own current_user method the way knock would have defined it. I'm opening this issue in case anyone else encounters this and to start a discussion to see about fixing it somehow.

Instead of using unless self.respond_to?(getter_name) to check if knock has defined the method we could set an instance variable so we know that knock has defined the method and not something else. This would remove the ability to define your own current_ method on purpose though.

commented

I've had to do something similar, but for Sorcery:

      # Override for Sorcery
      def authenticate_for entity_class
        getter_name = "current_#{entity_class.to_s.parameterize.underscore}"
        self.class.undef_method getter_name.to_sym # NOTE: undefine Sorcery's "current_user"
        define_current_entity_getter(entity_class, getter_name)
        public_send(getter_name)
      end

      # Override for Sorcery
      def define_current_entity_getter entity_class, getter_name
        return if respond_to?(getter_name)
        memoization_var_name = "@#{getter_name}" # NOTE: modified to conform to Sorcery's "@current_user" instance variable
        self.class.send(:define_method, getter_name) do
          unless instance_variable_defined?(memoization_var_name)
            current =
              begin
                ::Knock::AuthToken.new(token: token).entity_for(entity_class)
              rescue ::Knock.not_found_exception_class, JWT::DecodeError
                nil
              end
            instance_variable_set(memoization_var_name, current)
          end
          instance_variable_get(memoization_var_name)
        end
      end

Maybe a couple of config variables, one to define Knock's current_user method and another to allow Knock to override the underlying authentication library's current_user method, would be helpful.

Hi @kevinhughes27, I think you'll find this video useful.

ah so they use undef_method That would work too.

@kevinhughes27 I would be really interested in seeing how your defined your own current_user method because I'm currently facing exactly the same problem but am unsure how to go about solving it.

I have Devise powering authentication for ActiveAdmin but require Knock to authenticate my Rails API. At the moment my current_user is returning as nothing when the user authenticates which means I can't get the current user :(

Just to make my issue even more clear.

If I add undef_method :current_user then i'm receiving the error undefined method 'current_user' for class 'ApiController' and if I try to use before_action :authenticate_user I'm receiving an error that says Filter chain halted as :authenticate_user rendered or redirected. Completed 401 Unauthorized in 8ms.

I'm really not sure where to go from here so any help would be much appreciated.

Sure thing!

class ApiController < ActionController::API
  include Knock::Authenticable

  private

  # overwrite current_user added by devise with
  # a method for knock (gem would add this if it were not already defined)
  # https://github.com/nsarno/knock/issues/220
  def current_user
    @current_user ||= begin
      Knock::AuthToken.new(token: token).entity_for(User)
    rescue Knock.not_found_exception_class, JWT::DecodeError
      nil
    end
  end
end

Awesome thanks for this Kevin!!

My issue actually turned out to be nothing to do with Devise and I have it all working now but this is still super helpful for when I undoubtedly hit the issue at some point in the future 👍

Thanks again!