Skip to content

Setting Up with Multiple Providers

Erik-B. Ernst edited this page May 13, 2022 · 9 revisions

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

User.rb

devise :saml_authenticatable, :trackable

This wiki is assuming your devise model is called User

devise.rb initializer:

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 here for your users. It is helpful to document these and show them to your users. It can help them setup their provider.

attribute-map.yml

"email": "email"
"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. Other attributes are optional. Our implementation requires first and last name validation so we require it from our customer's provider.

Multiple Settings

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 attributes:

"id", "assertion_consumer_service_url", "assertion_consumer_service_binding", "name_identifier_format", "issuer", "idp_entitiy_id", "authn_context", "idp_slo_target_url", "idp_sso_target_url", "idp_cert_fingerprint", "organization_id", "created_at", "updated_at", "idp_cert_fingerprint_algorithm", "idp_entity_id"

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 you must require: idp_slo_target_url idp_cert_fingerprint 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.

Create a file called id_p_settings_adapter.rb in your lib 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.

Routes.rb

In the routes we will override much of the default behavior and enable the use of saml_sessions controller. That controller code is below.

  ...
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
...

saml_sessions_controller.rb

In the saml_sessions controller we will define the idp_setup that will be sent to the idp_settings_adapter for use. 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.

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.type = "SampleUser"
           current_user.save
        end

        super
    end


    private

    def store_winning_strategy
        warden.session(:user)[:strategy] = warden.winning_strategies[:user].class.name.demodulize.underscore.to_sym
    end
end

Working Okta

The idp_setup

The okta setup

One Login

The idp_setup

The One Login Setup

Clone this wiki locally