Devise Integration Example #70

Closed
JoshuaNovak919 opened this Issue May 18, 2016 · 7 comments

Comments

Projects
None yet
7 participants
@JoshuaNovak919

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.

@nsarno

This comment has been minimized.

Show comment
Hide comment
@nsarno

nsarno May 19, 2016

Owner

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

Owner

nsarno commented May 19, 2016

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

@nsarno nsarno closed this May 19, 2016

@amiuhle

This comment has been minimized.

Show comment
Hide comment
@amiuhle

amiuhle Aug 18, 2016

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 commented Aug 18, 2016

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...

@nsarno

This comment has been minimized.

Show comment
Hide comment
@nsarno

nsarno Aug 23, 2016

Owner

@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.

Owner

nsarno commented Aug 23, 2016

@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.

@raviada

This comment has been minimized.

Show comment
Hide comment
@raviada

raviada Sep 16, 2016

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 commented Sep 16, 2016

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.

@kevinelliott

This comment has been minimized.

Show comment
Hide comment
@kevinelliott

kevinelliott Mar 26, 2017

@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.

@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.

@nicnilov

This comment has been minimized.

Show comment
Hide comment
@nicnilov

nicnilov Nov 16, 2017

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?

nicnilov commented Nov 16, 2017

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?
@duykhoa

This comment has been minimized.

Show comment
Hide comment
@duykhoa

duykhoa Dec 25, 2017

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

duykhoa commented Dec 25, 2017

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

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment