Search code examples
ruby-on-rails-3omniauth

Scoped Omniauth routes


Is it possible to scope omniauth paths to a resource? For example, let's say we have an arbitrary website building website, where I can scope the website in routes via something like:

get ":site_name", :to => "sites#show"

I now want to add authentication via omniauth, but have it work on each individual site, so if I could specify the omniauth routes manually, it might be:

get ":site_name/auth/:provider", :to => "omniauth#whatever"
get ":site_name/auth/failure", :to => "omniauth#failure"
get ":site_name/auth/:provider/callback", :to => "my_omniauth_callbacks#auth"

We have tried using the path_prefix like so:

Rails.application.config.middleware.use OmniAuth::Builder do
  configure do |config|
    config.path_prefix = "/:site_name/auth"
  end

  # Providers
end

but it just means we have to visit /:site_name/auth/provider instead of being able to use /my_site_1/auth/provider


Solution

  • We were able to accomplish this with the following omniauth initializer (including configuration for google openid provider, though the same concept would probably apply to other providers):

    require "openid/store/filesystem"
    
    Rails.application.config.middleware.use OmniAuth::Builder do
      AUTH_REGEX = /^\/([^\/]+)\/auth\/([^\/]+)$/
      CALLBACK_REGEX = /^\/([^\/]+)\/auth\/([^\/]+)\/callback$/
      SITE_REGEX = /^\/([^\/]+)(?:\/auth\/([^\/]+)\/callback)?/
    
      configure do |config|
        config.on_failure = lambda do |env|
          match_data = SITE_REGEX.match env["PATH_INFO"]
    
          if match_data
            provider = match_data[2] || "unknown"
            location = "/#{match_data[1]}/auth/#{provider}/failure"
          else
            location = "/unknown/auth/unknown/failure"
          end
    
          Rack::Response.new(["302 Moved"], 302, "Location" => location).finish
        end
      end
    
      callback_path = lambda do |env|
        env["PATH_INFO"] =~ CALLBACK_REGEX
      end
    
      request_path = lambda do |env|
        match_data = AUTH_REGEX.match env["PATH_INFO"]
    
        if match_data
          "/#{match_data[1]}/auth/#{match_data[2]}/callback"
        end
      end
    
      provider :openid, :name => "google",
                        :identifier => "https://www.google.com/accounts/o8/id",
                        :store => OpenID::Store::Filesystem.new("/tmp"),
                        :callback_path => callback_path,
                        :request_path => request_path
    end
    

    This will utilize the site name in the callback, the original auth link, as well as the failure link. It can then be paired with the following routes in routes.rb:

    get "/:site_name/auth/:provider/failure", :to => "my_omniauth#failure"
    post "/:site_name/auth/:provider/callback", :to => "my_omniauth#callback"