skip to Main Content

I have a Rails 5 site which consists of 2 parts:

  1. Admin area
  2. API-only client area

I’m using Devise for both parts and https://github.com/lynndylanhurley/devise_token_auth gem for the API frontend.

The problem is about using the omniauth authentication. When I omniauth authenticate into the admin area – everything is ok – I get back some successful HTML-response.

But the problem is that I’m getting the same HTML-response in the API-area – but I need some JSON-response – not HTML one.

Here is my code:

config/routes.rb

Rails.application.routes.draw do

    devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }

    namespace :api do   

        mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions', :omniauth_callbacks => 'api/users/omniauth_callbacks' }    

    end
end

app/models/user.rb

class User < ApplicationRecord

  # Include default devise modules.
  devise :database_authenticatable, :registerable,
          :recoverable, :rememberable, :trackable, :validatable,
          :omniauthable,
          :omniauth_providers => [:facebook, :vkontakte]

  include DeviseTokenAuth::Concerns::User

  devise :omniauthable

  def self.from_omniauth_vkontakte(auth)
    where(provider: auth.provider, uid: auth.uid).first_or_create do |user|
      user.email = auth.extra.raw_info.first_name.to_s + "." + auth.extra.raw_info.last_name.to_s + '@vk.com'      
      user.password = Devise.friendly_token[0,20]
    end
  end

end

app/controllers/users/omniauth_callbacks_controller.rb

class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController

  def vkontakte
    @user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])

    if @user.persisted?
      sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
      set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
    else
      session["devise.vkontakte_data"] = request.env["omniauth.auth"]
      redirect_to new_user_registration_url
    end    
  end

end

config/initializers/devise.rb

Devise.setup do |config|

  config.omniauth :facebook, ENV["FACEBOOK_APP_ID"], ENV["FACEBOOK_APP_SECRET"], provider_ignores_state: true

  config.omniauth :vkontakte, ENV["VKONTAKTE_APP_ID"], ENV["VKONTAKTE_APP_SECRET"]

end

Gemfile

gem 'omniauth'
gem 'omniauth-facebook'
gem 'omniauth-vkontakte'

Gemfile.lock

devise (4.3.0)
devise_token_auth (0.1.42)

Here’s my log:

Started GET "/api/auth/vkontakte" for 127.0.0.1 at 2017-06-20 17:34:23
+0300 
Started GET "/omniauth/vkontakte?namespace_name=api&resource_class=User" for
127.0.0.1 at 2017-06-20 17:34:23 +0300 
I, [2017-06-20T17:34:23.237270 #15747]  INFO -- omniauth: (vkontakte) Request phase initiated. 
Started GET "/omniauth/vkontakte/callback?code=0b8446c5fe6873bb12&state=52254649eb899e3b743779a1a4afc0304f249a6dd90b4415" for 127.0.0.1 at 2017-06-20 17:34:23 +0300 
I, [2017-06-20T17:34:23.672200 #15747]  INFO -- omniauth: (vkontakte) Callback phase initiated. Processing by Users::OmniauthCallbacksController#vkontakte as */*   Parameters: {"code"=>"0b8446c5fe6873bb12", "state"=>"52254649eb899e3b743779a1a4afc0304f249a6dd90b4415"}

I guess that the problem is about a so-called “callback” url. I don’t understand where it is set. It is obvious from the log that at the end of the auth process the GET "/omniauth/vkontakte/callback..." query is called. And probably it is called always – no matter if I initiated the oath sequence from admin or api client area.

I use Chrome Postman to make the API query http://localhost:3000/api/auth/vkontakte – and I get the HTML-response back (“successful login etc.”) – but I need surely some JSON-response.

Is there a way to dynamically change the callback path depending on some precondition?
Is the callback query somewhat different depending on from where the oath procedure was initiated?

EDIT1:

This is not a single problem here unfortunately. Looks like the oauth is simply not implemented in the https://github.com/lynndylanhurley/devise_token_auth gem. So, even if I succeed to switch the oauth login procedure to the JSON way – how do I login the user the devise_token_auth-way – generating 3 tokens etc…? The app/controllers/users/omniauth_callbacks_controller.rb needs to be totally reimlemented.

2

Answers


  1. Chosen as BEST ANSWER

    I ended up implementing my own oauth callback procedure - instead of using one from the devise_token_auth gem.

    The devise_token_auth gem does contain the oauth authentication - but it appears to be not working properly.

    Here are my code changes:

    config/routes.rb

    Rails.application.routes.draw do
    
        devise_for :users, controllers: { sessions: 'users/sessions', :omniauth_callbacks => 'users/omniauth_callbacks' }
    
        namespace :api do   
    
            mount_devise_token_auth_for 'User', at: 'auth', controllers: { sessions: 'api/users/sessions'}   
    
        end
    end
    

    app/controllers/users/omniauth_callbacks_controller.rb

    class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
      include DeviseTokenAuth::Concerns::SetUserByToken
    
      def vkontakte
        @user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
    
        namespace_name = request.env["omniauth.params"]["namespace_name"]
    
        if @user.persisted?
    
          if namespace_name && namespace_name == "api"
    
            @client_id = SecureRandom.urlsafe_base64(nil, false)
            @token     = SecureRandom.urlsafe_base64(nil, false)
    
            @user.tokens[@client_id] = {
              token: BCrypt::Password.create(@token),
              expiry: (Time.now + DeviseTokenAuth.token_lifespan).to_i
            }
            @user.save
    
            @resource = @user # trade-off for "update_auth_header" defined in "DeviseTokenAuth::Concerns::SetUserByToken"
    
            sign_in(:user, @user, store: false, bypass: false)        
    
            render json: @user
    
          else
    
            sign_in_and_redirect @user, :event => :authentication #this will throw if @user is not activated
            set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
    
          end
    
        else
          session["devise.vkontakte_data"] = request.env["omniauth.auth"]
          redirect_to new_user_registration_url
        end    
      end
    
    end
    

    The inclusion of include DeviseTokenAuth::Concerns::SetUserByToken provides 5 auth headers in response:

    access-token →BeX35KJfYVheKifFdwMPag
    client →96a_7jXewCThas3mpe-NhA
    expiry →1499340863
    token-type →Bearer
    uid →376449571
    

    But the response still lacks these headers (available at a common sign-in):

    Access-Control-Allow-Credentials →true
    Access-Control-Allow-Methods →GET, POST, PUT, PATCH, DELETE, OPTIONS
    Access-Control-Allow-Origin →chrome-extension://aicmkgpgakddgnaphhhpliifpcfhicfo
    Access-Control-Max-Age →1728000
    

    I don't know whether they are important and if yes - how to provide them.

    PS The same identical approach works with Facebook too.


  2. You can render json from your OmniauthCallbacksController based on some extra parameter provided when your request a connection from the API for example.
    These extra parameters will be availables in this hash request.env["omniauth.params"].

    class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController
    
      def vkontakte
        @user = User.from_omniauth_vkontakte(request.env["omniauth.auth"])
    
        if @user.persisted?
          sign_in @user, :event => :authentication #this will throw if @user is not activated
          set_flash_message(:notice, :success, :kind => "Vkontakte") if is_navigational_format?
    
          if request.env["omniauth.params"]["apiRequest"]
            render status: 200, json: { message: "Login success" }
          else
            redirect_to after_sign_in_path_for(@user)
          end
        else
          session["devise.vkontakte_data"] = request.env["omniauth.auth"]
          redirect_to new_user_registration_url
        end
      end
    
    end
    

    You can this extra parameters by calling the auth helper with additional parameters, they will be passed to your OmniauthController : user_vkontakte_omniauth_authorize_path(api_request: true) (Or whatever your route helper is)

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search