Skip to content

Instantly share code, notes, and snippets.

@jturkel
Last active March 24, 2021 04:51
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 3 You must be signed in to fork a gist
  • Save jturkel/8c796708b76c6385c4ee to your computer and use it in GitHub Desktop.
Save jturkel/8c796708b76c6385c4ee to your computer and use it in GitHub Desktop.
How to configurate omniauth-saml to work with multiple identity providers based on a dynamic URL path segment. A gem based on this idea has been extracted to https://github.com/salsify/omniauth-multi-provider-saml.
require 'omniauth'
require 'omniauth-saml'
class MultiProviderSamlHandler
UUID_REGEX = /[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/
attr_reader :path_prefix, :provider_name
def initialize(path_prefix: OmniAuth.config.path_prefix, provider_name: 'saml')
@path_prefix = path_prefix
@provider_name = provider_name
# Eagerly compute these since lazy evaluation will not be threadsafe
@provider_path_prefix = "#{path_prefix}/#{provider_name}"
@saml_path_regex = /^#{@provider_path_prefix}\/(?<identity_provider_id>#{UUID_REGEX})/
@request_path_regex = /#{saml_path_regex}\/?$/
@callback_path_regex = /#{saml_path_regex}\/callback\/?$/
end
def provider_options
{
path_prefix: path_prefix,
name: provider_name,
request_path: method(:request_path?),
callback_path: method(:callback_path?),
setup: method(:setup)
}
end
private
attr_reader :provider_path_prefix, :saml_path_regex, :request_path_regex, :callback_path_regex
def setup(env)
identity_provider_uuid = extract_identity_provider_id(env)
if identity_provider_uuid
options = env['omniauth.strategy'].options
add_path_options(options, identity_provider_uuid)
add_identity_provider_options(options, identity_provider_uuid)
end
end
def add_path_options(options, identity_provider_uuid)
options.merge!(
request_path: "#{provider_path_prefix}/#{identity_provider_uuid}",
callback_path: "#{provider_path_prefix}/#{identity_provider_uuid}/callback"
)
end
def add_identity_provider_options(options, identity_provider_uuid)
# Make this real based on lookup of IdentityProvider with uuid identity_provider_uuid
identity_provider = IdentityProvider.find_by(uuid: identity_provider_uuid)
if identity_provider
options.merge!(identity_provider.options)
else
raise OmniAuth::Strategies::SAML::ValidationError.new('Invalid identity provider id')
end
end
def request_path?(env)
path = current_path(env)
!!request_path_regex.match(path)
end
def callback_path?(env)
path = current_path(env)
!!callback_path_regex.match(path)
end
def current_path(env)
env['PATH_INFO']
end
def extract_identity_provider_id(env)
path = current_path(env)
match = saml_path_regex.match(path)
match ? match[:identity_provider_id] : nil
end
end
require 'omniauth'
Rails.application.config.middleware.use OmniAuth::Builder do
saml_handler = MultiProviderSamlHandler.new(path_prefix: '/users/auth')
saml_options = {
name_identifier_format: 'urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress',
skip_info: true
}.merge(saml_handler.provider_options)
provider :saml, saml_options
end
MyApplication::Application.routes.draw do
match '/users/auth/saml/:saml_provider_id/callback',
via: [:get, :post],
to: 'omniauth_callbacks#saml',
as: 'user_omniauth_callback'
match '/users/auth/saml/:saml_provider_id',
via: [:get, :post],
to: 'omniauth_callbacks#passthru',
as: 'user_omniauth_authorize'
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment