3

Here is the situation. I have a multi-tenant rails app using the apartment gem where I need to implement a LinkedIn OmniAuth Strategy.

As you can see by my routes, Devise users, and the associated routes, are only persisted on the individual schemas of the subdomains.

Example Route:

Good: https://frank.example.io/users/sign_in

Bad: https://example.io/users/sign_in

Routes

class SubdomainPresent
  def self.matches?(request)
    request.subdomain.present?
  end
end

class SubdomainBlank
  def self.matches?(request)
    request.subdomain.blank?
  end
end

Rails.application.routes.draw do
  constraints(SubdomainPresent) do

    ...

    devise_for :users, controllers: { 
      omniauth_callbacks: 'omniauth_callbacks'
    }
    devise_scope :user do
      get '/users/:id', to: 'users/registrations#show', as: "show_user"
    end

    ...

  end
end

My specific problem is that LinkedIn does not support wildcards with their callback URLs so I am lost on how I might be able to direct users to the right domain after OAuth authentication.

Will
  • 386
  • 2
  • 15
  • You would need to add a controller action in which you're redirecting the user to their specific user page. I guess the LinkedIn API returns some kind of `user_id` back? This is where you'll need to start. – CottonEyeJoe Feb 01 '17 at 08:03
  • Unfortunately, because of the schemas that might not be possible. If I had schemas `A` and `B`, each one could have a user with `user.id == 1`. – Will Feb 01 '17 at 19:06

2 Answers2

5

So it turns out the answer was to pass parameters in the authorization link which would eventually get passed to the callback action throught request.env["omniauth.params"]

Authorization Link format:

Here I was having trouble adding the parameters to the Devise URL builder so I just manually added the parameters. This can probably be moved to a url helper

<%= link_to "Connect your Linkedin", "#{omniauth_authorize_path(:user, :linkedin)}?subdomain=#{request.subdomain}" %>

Routes:

Then I defined a route constrained by a blank subdomain pointing to the callback action.

class SubdomainPresent
  def self.matches?(request)
    request.subdomain.present?
  end
end

class SubdomainBlank
  def self.matches?(request)
    request.subdomain.blank?
  end
end

Rails.application.routes.draw do
  constraints(SubdomainPresent) do
    ...
    devise_for :users, controllers: {
      omniauth_callbacks: 'omniauth_callbacks'
    }
    resources :users
    ...
  end

  constraints(SubdomainBlank) do
    root 'welcome#index'
    ...
    devise_scope :user do
      get 'linkedin/auth/callback', to: 'omniauth_callbacks#linkedin'
    end
    ...
  end
end

Controller:

I used this tutorial to set up my callback controllers: Rails 4 OmniAuth using Devise with Twitter, Facebook and Linkedin. My main objective with the callback controller was to have it reside in in the blank subdomain so I only had to give one call back URL to my LinkedIn Dev App. With this controller I search the omniauth params for the subdomain parameter and use that to switch to the proper schema.

def self.provides_callback_for(provider)
  class_eval %Q{
    def #{provider}
      raise ArgumentError, "you need a subdomain parameter with this route" if request.env["omniauth.params"].empty?

      subdomain = request.env["omniauth.params"]["subdomain"]
      Apartment::Tenant.switch!(subdomain)
      ...
    end
  }
end
Will
  • 386
  • 2
  • 15
1

could you register each domain as a callback with linked (I guess if you have a lot that becomes unmanageable quickly).. You could cookie the user before sending them to linkedin so when they return you know which subdomain they belong to.

  • Unfortunately subdomains are generated dynamically. However the cookie idea is interesting, that might be useful. – Will Feb 03 '17 at 23:23