lynndylanhurley / devise_token_auth

Token based authentication for Rails JSON APIs. Designed to work with jToker and ng-token-auth.
Do What The F*ck You Want To Public License
3.52k stars 1.14k forks source link

Having both devise and devise_token_auth #120

Open casertap opened 9 years ago

casertap commented 9 years ago

I have a lot of trouble to set up my application with both devise and devise_token_auth. We are slowly migrating from a rails server-side rendering to an angularjs application. The problem is that I want to have a way to log in and have part of my website with angular and part without angular (yet). It seems that with devise_token_auth the current_user is not statefull, that is a problem for me because I need to log in ones and then I just need to check if current_user exists. At least I need to do that until the migration is complete and I can totally switch to a token auth. Do you have an idea on how to solve my problem?

lynndylanhurley commented 9 years ago

I'm having a hard time understanding this issue. Can you please elaborate? For example, what do you mean by "stateful"?

Also, have you read this section of the README?

casertap commented 9 years ago

Ok I will try to be more clear. I actually have an application that use Devise to loggin/signin etc... So when I currently loggin, a new current_user is created and then I keep this user until he logout.

We are migrating page after page to angularJS so I was wondering if it is possible to have devise_token_auth on pages with angular and my standard Devise for my old pages. The thing is that I only want an unique way to loggin.

nickL commented 9 years ago

@casertap Yes, you can have normal (cookie) based devise for your legacy pages and, as you migrate to angular, use DeviseTokenAuth for your new angular pages. The way I'm handling it now is having the DeviseTokenAuth::Concerns::SetUserByToken check for a warden.user in session before checking the token headers. For old pages -- the user is authenticated via devise. For new angular pages the user is authenticated via DeviseTokenAuth (via ng-token-auth).

lynndylanhurley commented 9 years ago

The way I'm handling it now is having the DeviseTokenAuth::Concerns::SetUserByToken check for a warden.user in session before checking the token headers.

@nickL - that sounds like a good strategy. Should we incorporate this behavior into the gem?

nickL commented 9 years ago

@lynndylanhurley: Yeah, I think it may be useful. Here's a quick snippet. Let me know if I'm on the right track and I'll clean it up more and submit a PR:


  def set_user_by_token(mapping=nil)
    # determine target authentication class
    rc = resource_class(mapping)

    # no default user defined
    return unless rc

    # user has already been found and authenticated
    return @resource if @resource and @resource.class == rc

    # parse header for values necessary for authentication
    uid        = request.headers['uid']
    @token     = request.headers['access-token']
    @client_id = request.headers['client']
    @client_id ||= 'default'

    #****  Money line here...  looking for user via warden first..****
    #*********************************************************************
    #
    if warden.user
      user = warden.user
      user.provider ||= "email"
      user.uid ||= user.email
      user.save if user.changed?
      sign_in(:user, user, store: false, bypass: true)
      return @resource = user
   else
      return false unless @token
      # client_id isn't required, set to 'default' if absent

      # mitigate timing attacks by finding by uid instead of auth token
      user = uid && rc.find_by_uid(uid)
nickL commented 9 years ago

Crap, that user.provider= & user.uid stuff is specific to our implementation, worth ignoring.

lynndylanhurley commented 9 years ago

@nickL - looks good to me! If you have time to send a PR I'll merge ASAP.

nickL commented 9 years ago

Cool! I'll whip up a PR and add some tests. Thanks dude!

On Wed, Mar 25, 2015 at 12:26 PM -0700, "Lynn Dylan Hurley" notifications@github.com wrote:

@nickL - looks good to me! If you have time to send a PR I'll merge ASAP.

— Reply to this email directly or view it on GitHub.

casertap commented 9 years ago

@nickL it is an awesome solution thanks a lot. I can't wait for this feature to be included in the gem.

spemmons commented 8 years ago

I'm not sure if this is the best issue to comment on or if I should make a new one.

I am using ActiveAdmin together with this gem and have noticed that /admin/logout (which results in "active_admin/devise/sessions#destroy" and presumably maps to the normal Devise sign-out) sets "current_user" to nil, but /auth/sign_out does not. This is leading to some inconsistencies where I can sign out on using /auth/sign_out, but can directly go to my AA page at /admin and still be signed in...

Jeehut commented 8 years ago

There's now documentation in the Can I use this gem alongside standard Devise? section of the README. Doesn't this solve this issue?

rossshannon commented 8 years ago

The documentation on this is very difficult to piece together. I’d try to rewrite it but I haven’t gotten my standard Devise actions working again since adding the gem.

From the readme:

Yes! But you will need to enable the support use separate routes for standard Devise. So do something like this:

config/initializers/devise_token_auth.rb

DeviseTokenAuth.setup do |config|
 # enable_standard_devise_support = false
end

Why is the config line commented out? Shouldn’t it be config.enable_standard_devise_support = true intead of # enable_standard_devise_support = false

alexonozor commented 7 years ago

Can someone help me? What is the solution to this bug? I've been crawling around the internet.

addicted2sounds commented 7 years ago

I have the issue with the latest version. Original devise seems not to be working after adding this gem

JohnMerlino2 commented 7 years ago

For me, it gives me an error here:

  def set_user_by_token(mapping=nil)
    # determine target authentication class
    rc = resource_class(mapping)

The error message is:

wrong number of arguments (given 1, expected 0)

Apparently, Devise has changed that resource_class no longer accepts an argument? Is that is what is causing this to fail? Because right now, it never gets to the line if DeviseTokenAuth.enable_standard_devise_support

http://stackoverflow.com/questions/41902326/class-context-overrides-module-methods-with-the-same-name

JohnMerlino2 commented 7 years ago

It seems like a lot of people are having the same issue here, dating back to 2015 and we are now in January 2017. I think what happened is that the Devise gem made updates that adversely influenced the behavior of this gem. Since this gem is no longer maintained, this gem can no longer work in conjunction with Devise (for the web). I assume the solution now is to migrate to this gem: https://github.com/gonzalo-bulnes/simple_token_authentication

ziaulrehman40 commented 7 years ago

@JohnMerlino2 Is it true what you said: "Since this gem is no longer maintained, this gem can no longer work in conjunction with Devise (for the web)"?

Because this repo seems active and last commit was a month ago.

tit1 commented 7 years ago

I was having the same issue where i need to sign a user into both devise and devise-token-auth. First i set up my login page to be a standard Devise login page. I than extended Devise::SessionController#create:

def create
  super do |user|
    session[:token] = true
  end
end

In the ApplicationController I added:

 before_filter :set_token

def set_token
    if session[:token] && user_signed_in?
      newAuth = current_user.create_new_auth_token
      response.headers.merge!(newAuth)
      @auth_token = newAuth
      session.delete(:token)
    else
      @auth_token = false
    end
  end

Finally in views/layouts/application.html.haml i added:

- if @auth_token
  :coffee
    myApp.controller 'cookieController', ($scope, $resource, $http, $mdDialog, $auth, ipCookie ) ->
      ipCookie('auth_headers', "{\"access-token\": \"#{@auth_token['access-token']}\",\"token-type\": \"Bearer\",\"client\": \"#{@auth_token['client']}\",\"expiry\": \"#{@auth_token['expiry']}\",\"uid\": \"#{@auth_token['uid']}\"}", {path: "/",expires: 9999,expirationUnit: 'days',secure: false})
      ipCookie('currentConfigName', 'default', {path: "/",expires: 9999,expirationUnit: 'days',secure: false})
  %div{'ng-controller'=>'cookieController'}

I realize that this is a little hacky and that my code could be a lot cleaner; that said i wanted to share it with you as a possible solution for people looking to authenticate with both devise and devise-toke-auth.

jotolo commented 7 years ago

I was working in a similar case where I needed to use both, devise+devise_token_auth in a spree application. I was trying to implement authentication for spree_api and migrating the authentication of spree system(Full Stack App) to devise. I found a way to do it and it's very simple.It's not a common thing in rails but it works great. The idea is to have a main application_controller.rb in your application and particular application_controllers for each section. In my case one for admin system in spree and the other one for API.

#controllers/api/v1/application_controller.rb
module Api
  module V1
    class ApplicationController < ApplicationController
      skip_before_action :verify_authenticity_token
      include DeviseTokenAuth::Concerns::SetUserByToken
    end
  end
end

In the API controller I included the regular DeviseTokenAuth concern to manage auth in the app that controls this application_controller.rb. Note that this controller inherits from the main application_controller.

#controllers/admin/application_controller.rb
  module Admin
    class ApplicationController < ApplicationController
      before_action :authenticate_user!
    end
  end

This controller is, well, natural behavior of devise. I just needed to add the before action method. We just need the base application_controller ;)

class ApplicationController < ActionController::Base
  protect_from_forgery with: :exception, if: :verify_api

  def verify_api
    params[:controller].split('/')[0] != 'devise_token_auth'
  end
end

Our main application_controller.rb looks like this. You need to keep the CSRF token protection but you can avoid it when an API request arrive. The idea is to redirect each flow in your application. the ones that belongs to your full-stack app and the other ones that belongs to your API. Well, in this case to my API :smile: This scenario was tested using devise (4.2.0) and devise_token_auth (0.1.40). I hope I can help someone with this example. Regards

mrkrlli commented 6 years ago

I've created a PR to add @jotolo suggestion to the FAQ: https://github.com/lynndylanhurley/devise_token_auth/pull/1175