Search code examples
ruby-on-railsoauth-2.0devisejwtdoorkeeper

Implementing social login for Rails Api-only app


I'm struggling to find a working method to implement oauth2 login via Facebook & Google for my existing api-only rails app. Login flow & jwt management is done with Devise & Doorkeeper, following this guide.

I tried with Doorkeeper-grants-assertion examples, but none of them is working. The problem i have is that i can't exchange the provider's token with my jwt token.

Client side (Android and iOS apps) i can login with provider and get the token, but when i try to authorize the user to create a new token, it gives me errors.

The code is the same as examples. In the case of Google i'm skipping token request because i can already get it from client:

class GoogleController

  def initialize(auth_code)
    @auth_code = auth_code
    @user_data = user_data
  end

  def user_data
    url = "https://www.googleapis.com/oauth2/v3/userinfo?access_token=" + @auth_code
    response = Faraday.get(url, {:Accept => 'application/json'})
    @resp = JSON.parse(response.body)
  end

  def email
    @resp['email']
  end

  def first_name
    @resp['first_name']
  end

  def last_name
    @resp['last_name']
  end

  def get_user!
    # below you should implement the logic to find/create a user in your app basing on @user_data
    # It should return a user object
    user = User.find_by(email: email)
    if user
      Rails.logger.info "User"
      user
    else
      user = User.new(email: email, password: Devise.friendly_token.first(10))
      user.save
      Rails.logger.info "No User"
      user
    end
  end
end

I'm using postman to make requests, below there is the response if my body is:

{
"client_id": "doorkeeper_app_uid",
"client_secret": "doorkeeper_app_secret",
"grant_type": "assertion",
"provider": "google",
"assertion": "MY USER TOKEN" }

{ "error": "invalid_client",
"error_description": "Client authentication failed due to unknown client, no client authentication included, or unsupported authentication method." }

I just found out i didn't return an User object, that's why Facebook didn't work. Now, with the same code, only different token endpoint, Facebook login is working and i can find or create the user and return the jwt token, while Google is not.

If someone could point me in the right direction it would be great.

EDIT

after further investigation i'm at this point: i can find or create my Google authenticated user, but when i return it to doorkeeper assert grant extension, it fails validation

def validate_resource_owner
  !resource_owner.nil?
end

in class

PasswordAccessTokenRequest

and i can't generate new jwt token. What's different from facebook that makes this validation to fail?


Solution

  • Incredible guys, mystical things happens but i've found a solution.

    Somehow there was a conflict with 2 providers in doorkeeper.rb initializer if written like so: (Don't do this)

    resource_owner_from_assertion do
      if provider == "facebook"
        g = Api::V1::FacebookController.new(params[:assertion])
        g.get_user!
      end
      if provider == "google"
        g = Api::V1::GoogleController.new(params[:assertion])
        return g.get_user!
      end
    end
    

    instead do something like:

    resource_owner_from_assertion do
      provider = params[:provider]
      controller = (provider == "facebook") ? Api::V1::FacebookController.new(params[:assertion]) : Api::V1::GoogleController.new(params[:assertion])
      controller.get_user!
    end
    

    Then there was another issue inside controllers, because i used "user" as variable name:

    user = User.find_by(email: email)

    and this is apparently bad, so use

    facebook_user = User.find_by(email: email)

    Now everything seems to work as it is supposed to. I hope someone will find this useful.