Saml 2.0 Sso for Ruby on Rails

Integrate SAML in rails application

Do you want to:

  • a.) enable SAML Login from another service, like Microsoft Azure Directory, OneLogin etc.? (then you are an SP = Service Provider)
  • b.) your app has users and offers a Login service for other apps (IDP = Identity Provider)

I suppose it's a)?
Then it depends on: is it a provider per customer, so is it dynamically? Can each customer configure their own IDP, or is it one fixed for the whole app?
If latter, then I would strongly suggest to use omniauth-saml instead, which is much easier to configure.

But if you want to use Enterprise Sign In Per Customer, then here is how we do it.

  • First: I never wrangled with all the specific settings and urls, Saml can autoconfigure itself via "metadata url", We let our customers specify a metadata-uri, that uri has all the information we need, so we can parse it with the supplied class:
idp_metadata_parser = OneLogin::RubySaml::IdpMetadataParser.new
settings = idp_metadata_parser.parse_remote(organisation.idp_meta_data_url)

We then add our own settings + route information to it:

   settings.assertion_consumer_service_url = "https://#{request.host}/saml/consume/#{organisation.id}"
settings.issuer = "https://#{@request.host}/saml/metadata/#{organisation.id}"
settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
# Optional for most SAML IdPs
settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport"
# You would need a normal certificate/private key to enable signature stuff
settings.certificate = File.read('config/saml/certificate.crt')
settings.private_key = File.read('config/saml/private_key.key')
# In our case customer can optional activate signature validation:
if organisation.signature_enabled?
settings.security[:authn_requests_signed] = true # Enable or not signature on AuthNRequest
settings.security[:logout_requests_signed] = true # Enable or not signature on Logout Request
settings.security[:logout_responses_signed] = true # Enable or not signature on Logout Response
settings.security[:want_assertions_signed] = true # Enable or not the requirement of signed assertion
settings.security[:metadata_signed] = true # Enable or not signature on Metadata

settings.security[:digest_method] = XMLSecurity::Document::SHA1
settings.security[:signature_method] = XMLSecurity::Document::RSA_SHA1
end

Request / Response

these are more or less from the ruby-saml examples:

Controller:

  skip_before_action :verify_authenticity_token

def init
@saml_request = OneLogin::RubySaml::Authrequest.new
redirect_url = @saml_request.create(saml_settings)
if redirect_uri
redirect_to(redirect_uri)
else
@error = t('saml_controller.error')
render 'error'
end
end

def consume
@saml_response = OneLogin::RubySaml::Response.new(params[:SAMLResponse])
@saml_response.settings = saml_settings

if @saml_response.is_valid?
# do application logic, create user/update user sign in user
sign_in(....)
session[:session_valid_for] = 12.hours.from_now.to_i
redirect_to '/'
else
redirect_to '/watcher/profile'
end
else
@error = @saml_response.errors
render 'error'
end
end

Metadata

Most customers want a metadata-uri, too, to add the SP into their IDP (and configure that part automatically too), so you also need to provide a metadata, e.g. "/saml/#{org.id}/metadata" and return the metadata:

def metadata
meta = OneLogin::RubySaml::Metadata.new
render xml: meta.generate(saml_settings), content_type: "application/samlmetadata+xml"
end

Update: Using omniauth-saml

We also use Omniauth saml in an app. It is quite simple, but the config depends on the server you want to integrate with. You will need a sso target url (the consume url from the other side) and a certificate fingerprint or certificate for security. Also you can specify the "name Identifier", E-Mail or username or stuff like this.

Rails.application.config.middleware.use OmniAuth::Builder do
use OmniAuth::Strategies::SAML,
idp_sso_target_url: "??",
idp_slo_target_url: "??",
idp_cert_fingerprint: "??",
name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:persistent",
# or "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" for E-Mail
issuer: "YourAppName.com"

Have a look at omniauth-saml's good Readme doc for a list of all options. You can also request more attributes from OneLogin/IDP by using the request_attributes:

:request_attributes - Used to build the metadata file to inform the
IdP to send certain attributes along with the SAMLResponse messages.
Defaults to requesting name, first_name, last_name and email
attributes. See the OneLogin::RubySaml::AttributeService class in the
Ruby SAML gem for the available options for each attribute.

Update: Passing dynamic parameters to omniauth saml's redirect to Idp:

There seems to be no option in Omniauth-saml to have dynamic parameters, so you can try to patch/override the behavior. Omniauth-SAML is a Rack-middleware, so you only have access to the request object but not normal Rails stuff. If you trust your users (which you should not) then you can put the info in the param to omniauth: /auth/saml?something1=foo&bar=2, or you could encrypt the parameters with ActiveSupport Message Encryptor.

If you then know how to extract the dynamic parameters from the request you can apply this patch dynamically, because Ruby!

# put this into
#
# config/initializers/omniauth_patch.rb
#
module OmniauthPatch
def additional_params_for_authn_request
# here you should have access to the current request
# try around with binding.irb what you can do
binding.irb
# return parameters you want to pass to the saml redirect
{
email: email,
# ...
}
end
end

OmniAuth::Strategies::SAML.include OmniauthPatch

How to send the authenticated response while authenticating a user via SAML in Rails?

You can post the data, but do it in a way that resembles a redirect. The problem with a redirect being that the data is usually larger than can be accommodated in a browser acceptable url.

You need to do it this way so that the post comes from the user's browser rather than your server. That is, the post needs to take the user's browser session with it, so that the associated cookies and session data are submitted with the SAML token.

One solution is to use a self submitting form as shown within saml_tools_demo's indentifies#create view.

Have a look at the matching controller action to see how the data are constructed.

Rails detect if session was created by SSO/SAML

Two potential options I see are either at the point of authentication, you set something in the session that indicates the authentication type. You can drive the authentication behaviour from that.

Or there is potential you could send a SAML request with isPassive=true to the IdP, which will return a SAML response if the user has a valid session with the IdP. You could then drive your logout behaviour from that response.

SSO (Single sign-on) and ADFS (Active Directory Federation Services) with Ruby On Rails

ADFS handles two protocols - WS-Fed and SAML. ADFS 3.0 handles the Authorisation code grant in OAuth 2.0 (but not OpenID Connect),

So to get your Rails app. to talk to ADFS, it needs to support one of those protocols.

If it can do that, there is no need for any other server.

Perhaps something like ruby-saml.

Look here: SAML 2.0 SSO for Ruby on Rails?

The other approach is to use OAuth / REST to get to some intermediate federation server and then SAML / WS-Fed out the other side.

Auth0 and Ping-Federate support this kind of approach.

Rails - Onelogin ruby-saml integration issue +

I think the declaration should be like this: OneLogin::RubySaml according to the source code.

Ruby Rails Devise and SAML with Office 365

Finally figured it out: All the Devise examples have

settings.authn_context = ""

set. If I set it to

settings.authn_context = "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"

then the error disappears.



Related Topics



Leave a reply



Submit