We write about Ruby on Rails, React.js, React Native, remote work, open source, engineering and design.
Recently, we integrated our SAML service provider(SP) with multiple identity providers(IDPs) to facilitate Single sign-on(SSO) using Devise with OmniAuth.
Before we jump into the specifics, here is SAML definition from wikipedia.
Security Assertion Markup Language (SAML, pronounced sam-el) is an open standard for exchanging authentication and authorization data between parties, in particular, between an identity provider(IDP) and a service provider(SP).
Here is the official overview on how to integrate OmniAuth with Devise.
After following the overview,
this is how our
user.rb looked like.
# config file Devise.setup do |config| config.omniauth :saml, idp_cert_fingerprint: 'fingerprint', idp_sso_target_url: 'target_url' end #user.rb file devise :omniauthable, :omniauth_providers => [:saml]
The problem with above configuration is that it supports only one SAML IDP.
To have support for multiple IDPs, we re-defined files as below.
# config file Devise.setup do |config| config.omniauth :saml_idp1, idp_cert_fingerprint: 'fingerprint-1', idp_sso_target_url: 'target_url-1' strategy_class: ::OmniAuth::Strategies::SAML, name: :saml_idp1 config.omniauth :saml_idp2, idp_cert_fingerprint: 'fingerprint-2', idp_sso_target_url: 'target_url-2' strategy_class: ::OmniAuth::Strategies::SAML, name: :saml_idp2 end #user.rb file devise :omniauthable, :omniauth_providers => [:saml_idp1, :saml_idp2]
Let's go through the changes one by one.
1. Custom Providers:
Instead of using standard provider
we configured custom providers (
in the first line of configuration
as well as in
2. Strategy Class:
In case of the standard provider(
Devise can figure out
on its own.
For custom providers,
we need to explicitly specify it.
3. OmniAuth Unique Identifier:
After making the above two changes,
everything worked fine
except OmniAuth URLs.
For some reason, OmniAuth was still listening
saml scoped path
instead of new provider names
# Actual metadata path used by OmniAuth /users/auth/saml/metadata # Expected metadata path /users/auth/saml_idp1/metadata /users/auth/saml_idp2/metadata
After digging in
OmniAuth code bases,
we discovered provider
In the absence of this configuration,
OmniAuth falls back to strategy class name
to build the path.
As we could not find any code in Devise
saml scoped path
(we were expecting Devise to pass
assigning same value as
OmnitAuth started listening to the correct URLs.
4. Callback Actions:
Lastly, we added both actions in
class Users::OmniauthCallbacksController < Devise::OmniauthCallbacksController def saml_idp1 # Implementation end def saml_idp2 # Implementation end # ... # Rest of the actions end
With these changes along with the official guide mentioned above, our SP was able to authenticate users from multiple IDPs.