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.55k stars 1.13k forks source link

Omniauth without external window #654

Open killerham opened 8 years ago

killerham commented 8 years ago

My users want to login with facebook and I have it setup where they can log into facebook using Facbook SDK on iOS. I want to pass the oauth access_token from SDK to omniauth, skipping the external window that devise_token_auth handles.

My question is how can I send an API request to /auth/:provider to go through the omniauth process when I already have an omniauth token?

I also am using the facebook-access-token gem which will handle the oauth token.

Related issues: https://github.com/lynndylanhurley/devise_token_auth/issues/556 https://github.com/lynndylanhurley/devise_token_auth/pull/323 https://github.com/lynndylanhurley/devise_token_auth/issues/175

Any advice is appreciated!

killerham commented 8 years ago

Anyone have any tips on this?

killerham commented 8 years ago

Any solutions are appreciated!

glapworth commented 8 years ago

I couldn't find a solution to this using devise_token_auth, and eventually gave up and used devise along with the tiddle gem.

I've uploaded an example rails app, along with an android app currently using Google oAuth2 for authentication. I wonder if that might help you?

https://github.com/glapworth/boilerplate-android-rails-api-oauth

killerham commented 8 years ago

@glapworth I didn't want to re-implement a bunch of my tests and different API setups. However I did find a solution!

Send a GET request to omniauth/:provider/callback with "access_token": token, "resource_class": "User"} as params.

Override devise's OmniAuthCallbacksController not token_auth's.

def self.provides_callback_for(provider)
class_eval %Q{
      def #{provider}

        if request.format == 'json'
          # derive target redirect route from 'resource_class' param, which was set
          # before authentication.
          redirect_route = "/api/v2/auth/#{provider}/callback?\#{params.to_param}"

          # preserve omniauth info for success route. ignore 'extra' in twitter
          # auth response to avoid CookieOverflow.
          session['dta.omniauth.auth'] = request.env['omniauth.auth'].except('extra')
          session['dta.omniauth.params'] = request.env['omniauth.params']

          redirect_to redirect_route

          return
        end
        # else do standard devise stuff
    end
    }
  end

[:twitter, :facebook, :facebook_access_token, :linked_in].each do |provider|
    provides_callback_for provider
  end

As you can see... this method is super hacky... I hardcoded the redirect route. If anyone has a better suggestion for this that would be great! Most of the code was taken from DeviseTokenAuth::OmniauthCallbacksController #redirect_callbacks

philippe3973 commented 8 years ago

+1, waiting for any more answers.

mohamedelfiky commented 8 years ago

+1

augustosamame commented 8 years ago

+1

tobeee commented 8 years ago

Did anyone find a better solution for this?

aaronshim commented 8 years ago

+1

Charlie-Hua commented 7 years ago

I just created 2 pull request for this purpose, FYI! lynndylanhurley/ng-token-auth#341 lynndylanhurley/devise_token_auth#793

mmahalwy commented 7 years ago

+1 on this.

gp3gp3gp3 commented 7 years ago

I'm confused. It seems as though one of the main attractions for using token authentication is being able to authenticate a user on a phone using something like facebook-sdk on the client. Is there some standard way to authenticate a mobile user on my rails server that I'm not seeing? I was under the impression that devise is more or less a standard for this, but it feels like I'm jumping through a lot of hoops here( overriding devise controller methods ) just to make this gem not use a redirect for omniauth, and ultimately being able to play nice with the iOS/Android facebook-sdk. If this isn't the standard what is? Or do most people just roll their own authentication entirely?

geoandri commented 7 years ago

Any news on this?

vexsnare commented 7 years ago

+1 Any update on this? Or is there any other alternative for those who are using rails as their backend API and using FACEBOOK login in their mobile APP.

I am stuck at this, I am working on something similar to Tinder facebook login where I need to create account in backend once I get fb_token.

hugohow commented 7 years ago

+1

geoandri commented 7 years ago

I have implemented a basic working solution on this using the Koala Gem. I created a route for this purpose eg 'customer_facebook_auth'. Then in the relevant controller using the facebook token that the mobile app acquired I create the new user (customer in my case) with the facebook user attributes. The facebook user attributes can be fetched using the Koala library gem.

class Api::V1::CustomerFacebookAuthController < ApplicationController
  def login_customer
    @graph = Koala::Facebook::API.new(params['access_token'])
    @f_profile = @graph.get_object('me', fields: ['email', 'id', 'name'])

    create_and_sign_in_user @f_profile
  end

  private

  def create_and_sign_in_user facebook_profile
    if Customer.where(provider: 'email', email: facebook_profile['email']).count > 0
      render json: 'This email has already been taken'
    else
      @resource = Customer.where(
        uid: facebook_profile['id'],
        provider: 'facebook'
      ).first_or_initialize

      p = SecureRandom.urlsafe_base64(nil, false)
      @resource.password = p
      @resource.password_confirmation = p

      @resource.name = facebook_profile['name']
      @resource.email = facebook_profile['email']

      @token = SecureRandom.urlsafe_base64(nil, false)

      @client_id = SecureRandom.urlsafe_base64(nil, false)
      @token = SecureRandom.urlsafe_base64(nil, false)

      @resource.tokens[@client_id] = {
        token: BCrypt::Password.create(@token),
        expiry: (Time.now.utc + DeviseTokenAuth.token_lifespan).to_i
      }

      @resource.save
    end
    render json: {
      data: resource_data(resource_json: @resource.customer_token_validation_response)
    }
  end

  def resource_data(opts={})
    opts[:resource_json] || @resource.as_json
  end
end

Hope it helps.

gibo commented 7 years ago

is there an official solution for this?

zachfeldman commented 7 years ago

@gibo there is not, but we do take pull requests! We'd love to see yours.

gibo commented 7 years ago

@zachfeldman seems like there is already a PR for this :) https://github.com/lynndylanhurley/devise_token_auth/pull/793

zachfeldman commented 7 years ago

Ah, I didn't realize this was the same thing! Sorry about that @gibo .

Have you tested the code in the open PR? If you've tested it along with the code that's currently in beta: https://github.com/lynndylanhurley/devise_token_auth/issues/972#issuecomment-344097692

then we may merge that PR sooner rather than later.

gibo commented 7 years ago

@zachfeldman I’ll give it a test

gibo commented 7 years ago

I made a small change to @geoandri code, as it was not returning the correct headers

def create_and_or_sign_in_profile facebook_profile
  @resource = Profile.find_by email: facebook_profile['email']

  @client_id = request.headers['client']    

  if @resource.nil?
    @resource = Profile.create(provider: 'email', uid: facebook_profile['id'], email: facebook_profile['email'])
    p = SecureRandom.urlsafe_base64(nil, false)
    @resource.password = @resource.password_confirmation = p
    @resource.name = facebook_profile['name']
    @resource.email = facebook_profile['email']
  end

  @token = SecureRandom.urlsafe_base64(nil, false)

  @resource.tokens[@client_id] = {
    token: BCrypt::Password.create(@token),
    expiry: (Time.now.utc + DeviseTokenAuth.token_lifespan).to_i
  }

  auth_header = @resource.create_new_auth_token @client_id
  response.headers.merge!(auth_header)

  @resource.save

  render json: {
    data: resource_data(resource_json: @resource.token_validation_response)
  }
end
zachfeldman commented 7 years ago

Nice @gibo. Want to maybe submit a clean PR if you're done testing?

gibo commented 7 years ago

I don't think it can really be tidied up into a PR, it really seems that Omniauth cannot be used with rails-api projects and therefore devise token auth.

I think there needs to be a new gem created to handle this scenario which integrates into devise token auth.

but for the time being perhaps the code above could be put into the read me?