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