-
Notifications
You must be signed in to change notification settings - Fork 164
Setting Up with Multiple Providers
To setup multiple providers start by following the getting started guide. The following is a greatly reduced adaption of our implementation. Okta and Onelogin samples are included at the end of the wiki.
gem 'devise_saml_authenticatable'
bundle install
devise :saml_authenticatable, :trackable
This wiki assumes your devise model is called User
require 'id_p_settings_adapter'
Devise.setup do |config|
config.saml_create_user = true
config.saml_update_user = true
config.saml_default_user_key = :email
config.saml_session_index_key = :session_index
config.saml_use_subject = true
config.idp_settings_adapter = IdPSettingsAdapter
config.saml_configure do |settings|
settings.assertion_consumer_service_url = ""
settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient"
settings.issuer = ""
settings.authn_context = ""
settings.idp_slo_target_url = ""
settings.idp_sso_target_url = ""
settings.idp_cert_fingerprint = ''
settings.idp_cert_fingerprint_algorithm = 'http://www.w3.org/2000/09/xmldsig#sha256'
end
Note the idp_settings_adapter
statement. We will come back to this soon.
We are providing a few defaults (assertion_consumer_service_binding
, name_identifier_format
, and idp_cert_fingerprint_algorithm
) here for your customers. It could be helpful to document these and provide them to your customers. It can help them set up their IdP provider application.
"email": "email" # key is the IdP name and value is the column it is mapped to in your application
"lastName": "last_name"
"firstName": "first_name"
Again, you will need to document and tell your users to add email
as an attribute or parameter depending on their setup. This is based on the saml_default_user_key
setting in devise.rb above. Other attributes are optional and depend on what makes instances of your User model valid. Our implementation has a first name and last name validation, so we require it from our customer's provider.
For allowing users to self service their own IDP we use a tenancy strategy, but you can do this however you want.
We have a IdPSetup
model with the following attributes:
id
assertion_consumer_service_url
assertion_consumer_service_binding
name_identifier_format
issuer
idp_entity_id
authn_context
idp_slo_target_url
idp_sso_target_url
idp_cert_fingerprint
organization_id
idp_cert_fingerprint_algorithm
created_at
updated_at
This model belongs to an organization. We reference the organization later and use these attributes to override the settings in the devise.rb initializer.
For a bare bones setup, at the minimum, you must have:
-
idp_sso_target_url
# where the user is redirected to for login at the IdP -
idp_cert_fingerprint
# for authentication - https://www.samltool.com/fingerprint.php is helpful for this -
idp_entity_id
# This is used to find the idp_setup and organization when the provider sends the user back. Entity ID, or SAML Issuer ID would go here to be referenced later. This is automatically passed into theidp_settings_adapter
from the SAML payload OR from your overridden aions in saml_sessions_controller.rb (see below). As an example, in Okta, they call it theSAML Issuer ID
under 'Advanced Settings' when in theEdit SAML Integration
view.
- Next, create a file called
id_p_settings_adapter.rb
in yourlib
directory.
class IdPSettingsAdapter
def self.settings(idp_entity_id)
# Find your settings. Here we identify our tenant (Organization class)
tenant = IdpSetup.find_by_idp_entity_id(idp_entity_id).organization
# Urls below are for a development enviroment
if tenant.present?
{
assertion_consumer_service_url: "http://#{tenant.short_name.parameterize}.localhost:3000/auth",
assertion_consumer_service_binding: tenant.idp_setup.assertion_consumer_service_binding.present? ? tenant.idp_setup.assertion_consumer_service_binding : "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST",
name_identifier_format: tenant.idp_setup.name_identifier_format.present? ? tenant.idp_setup.name_identifier_format : "urn:oasis:names:tc:SAML:2.0:nameid-format:transient",
issuer: "http://#{tenant.short_name.parameterize}.localhost:3000/metadata",
authn_context: "",
idp_slo_target_url: "",
idp_sso_target_url: tenant.idp_setup.idp_sso_target_url,
idp_cert_fingerprint: tenant.idp_setup.idp_cert_fingerprint,
idp_cert_fingerprint_algorithm: tenant.idp_setup.idp_cert_fingerprint_algorithm.present? ? tenant.idp_setup.idp_cert_fingerprint_algorithm : 'http://www.w3.org/2000/09/xmldsig#sha256'
}
else
{}
end
end
end
In the above example, you must update your assertion_consumer_service_url
& issuer
for your production environment.
In the routes.rb
file, we will override much of the default behavior and enable the use of a custom SamlSessionsController
.
...
devise_for :users, skip: [:registrations, :saml_authenticatable], controllers: { sessions: 'user/sessions' }
as :user do
get 'users/edit' => 'devise_registrations#edit', as: 'edit_registration'
patch 'users' => 'devise_registrations#update', as: 'registration'
# To avoid conflict, saml_authenticatable routes are manually defined here.
# Also we override the controller.
resource :session, as: 'saml_session', only: [], controller: 'saml_sessions', path: '/' do
get :new, path: 'sign_in', as: 'new'
match :destroy, path: 'sign_out', as: 'destroy', via: :get
post :create, path: 'auth'
get :metadata, path: 'metadata'
match :idp_sign_out, path: 'idp_sign_out', via: [:get, :post]
end
end
...
SamlSessionsController (saml_sessions_controller.rb)
In the saml_sessions_controller.rb
we will define the idp_setup
that will be sent to the idp_settings_adapter
for use in the new action. We also use the :store_winning_strategy provided in one of the issue resolutions.
In the create session, you can add attributes specific to your application if needed.
class SamlSessionsController < Devise::SamlSessionsController
after_filter :store_winning_strategy, only: :create
def new
request = OneLogin::RubySaml::Authrequest.new
action = request.create(saml_config(current_tenant.idp_setup.idp_entity_id))
redirect_to action
end
def create
if current_user.type.blank?
current_user.update(type: "SampleUser")
end
super
end
private
def store_winning_strategy
warden.session(:user)[:strategy] = warden.winning_strategies[:user].class.name.demodulize.underscore.to_sym
end
end
The idp_setup
The Okta setup
The idp_setup
The One Login Setup