Knock: Devise Integration Example

Created on 18 May 2016  路  8Comments  路  Source: nsarno/knock

Anyone try integrating this with Devise yet? I want Devise for it's user and password management, but need JWT since I have an API.

Most helpful comment

In case someone else is trying to get this to work:

#####
# api/base_controller.rb
#####

include Knock::Authenticable
before_action :authenticate_user
# devise defines this, while knock is using `method_missing`
undef_method :current_user

#####
# models/user.rb
#####

# Knock requires :authenticate
alias_method :authenticate, :valid_password?

# Returns the user stored in the payload's subject
def self.from_token_payload payload
  self.find payload["sub"]
end

Worked for me this way, but in the end I went with a custom solution since it's not a big deal to do this from scratch with the jwt gem, giving more flexibility for devise integration...

All 8 comments

Knock does not aim to be compatible with Devise and I would not recommend mixing both.

In case someone else is trying to get this to work:

#####
# api/base_controller.rb
#####

include Knock::Authenticable
before_action :authenticate_user
# devise defines this, while knock is using `method_missing`
undef_method :current_user

#####
# models/user.rb
#####

# Knock requires :authenticate
alias_method :authenticate, :valid_password?

# Returns the user stored in the payload's subject
def self.from_token_payload payload
  self.find payload["sub"]
end

Worked for me this way, but in the end I went with a custom solution since it's not a big deal to do this from scratch with the jwt gem, giving more flexibility for devise integration...

@amiuhle Thank you for this. I also agree with you, integrating knock + devise doesn't have much value. I would recommand using one or the other but not both at the same time. I reckon the best use case for knock is if you're relying on JWT for authentication exclusively.

The only thing I can think of using Devise is for generating forgot password tokens and links, other than that I see no need to have Devise. If we already have Devise integration, what would be the best way to remove Devise ad yet support "Forgot Password" flows.

@raviada You need to roll your own mailers, invitations, etc if you use this exclusively for auth. This is not an ideal gem if you have a full UI as well as an API.

In my case I have to accept API requests using the same controllers from both the browser and native apps. As controllers' classes are cached, after current_user gets replaced by Knock as per @amiuhle's solution, subsequent requests coming from the browser do not have access to Devise's current_user implementation anymore. So here is my take.

#####
# api/base_controller.rb
#####

include Knock::Authenticable

before_action :authenticate_user
before_action :skip_session

# JWT: Knock defines it's own current_user method unless one is already
# defined. As controller class is cached between requests, this method
# stays and interferes with a browser-originated requests which rely on
# Devise's implementation of current_user. As we define the method here,
# Knock does not reimplement it anymore but we have to do its thing
# manually.
def current_user
  if token
    @_current_user ||= begin
      Knock::AuthToken.new(token: token).entity_for(User)
    rescue
      nil
    end
  else
    super
  end
end

private

# JWT: No need to try and load session as there is none in an API request
def skip_session
  request.session_options[:skip] = true if token
end

# JWT: overriding Knock's method to manually trigger Devise's auth.
# When there is no token we assume the request comes from the browser so
# has a session (potentially with warden key) attached.
def authenticate_entity(entity_name)
  if token
    super(entity_name)
  else
    current_user
  end
end

#####
# models/user.rb
#####

# JWT: Authentication within an API request
alias authenticate valid_password?

@amiuhle Thanks for saving so many life. I almost think of rewriting all the functions of devise 馃槗

When I add undef_method :current_user I receive the following error: ActionController::RoutingError (undefined method 'current_user' for class 'ApiController'):.

However, if I try to include before_filter :authenticate_user then I receive the error Filter chain halted as :authenticate_user rendered or redirected. Completed 401 Unauthorized in 4ms (ActiveRecord: 0.0ms)

I am using Devise to power authentication for ActiveAdmin and am using Knock to add authorisation to my API.

I somehow need to re-write authenticate_user so that it doesn't just try to use the Devise method but am struggling to work out how.

Any help would be much appreciated!

Was this page helpful?
0 / 5 - 0 ratings

Related issues

psantos10 picture psantos10  路  3Comments

saroar picture saroar  路  3Comments

YMonnier picture YMonnier  路  3Comments

ghost picture ghost  路  4Comments

andyrue picture andyrue  路  4Comments