5

I am trying to add the ruby-saml in my project. But I am a bit confused about how to implement it in my scenario. I am on a website let's say abc.com and there is a button. When I click on the button, I need to redirect to the website xyz.com where I need to pass SAML XML and send it to xyz.com/SAML. The SAML request will be processed by xyz.com and then they will send me a response. Could anyone give me some idea how to achieve it?

Also, I am confused about these fields. Could someone give me a quick summary?

settings.assertion_consumer_service_url
settings.sp_entity_id
settings.idp_entity_id
settings.idp_sso_service_url
settings.idp_slo_service_url

def init
  request = OneLogin::RubySaml::Authrequest.new
  saml_settings.name_identifier_value_requested = "testuser@example.com"
  saml_settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress"
  redirect_to(request.create(saml_settings))
end



 def saml_settings
  settings = OneLogin::RubySaml::Settings.new

  settings.assertion_consumer_service_url = "http://#{request.host}/saml/consume"
  settings.idp_sso_service_url            = "https://app.onelogin.com/trust/saml2/http-post/sso/#{OneLoginAppId}"
  settings.idp_sso_service_binding        = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect
  settings.idp_slo_service_binding        = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
  settings.idp_cert_fingerprint           = OneLoginAppCertFingerPrint
  settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1"
  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"
  # or as an array
  settings.authn_context = [
    "urn:oasis:names:tc:SAML:2.0:ac:classes:PasswordProtectedTransport",
    "urn:oasis:names:tc:SAML:2.0:ac:classes:Password"
  ]

  # Optional bindings (defaults to Redirect for logout POST for ACS)
  settings.single_logout_service_binding      = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-Redirect" # or :post, :redirect
  settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" # or :post, :redirect

  settings
end
Aniket Tiwari
  • 3,561
  • 4
  • 21
  • 61

1 Answers1

8

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
Aniket Tiwari
  • 3,561
  • 4
  • 21
  • 61
stwienert
  • 3,402
  • 24
  • 28
  • Thanks for the detailed explanation. As per your explanation, I guess I need omniauth-saml because when I click the button I need to redirect to another website where they will check my request is valid or not. Based on that the other team will decide whether I am a genuine user or not. I only have one last question If I choose omniauth-saml How can I pass custom parameters in the request such as name, email and some custom attributes – Aniket Tiwari Sep 14 '21 at 15:52
  • I've updated my answer to some pointers for Omniauth-saml. I am not totally understand you: YOUR app wants to pass custom attributes to OneLogin, or do you want to receive the attributes from OneLogin after validating the user? (E-Mail, last name, first-name) If latter: That is already handled by Omniauth-saml. If Former... you'll need to patch omniauth-saml. – stwienert Sep 15 '21 at 06:52
  • Thanks for the response. Let me rephrase my question. There are 2 companies. Website 1 has already Implemented SAML. And website 2 (which is me) in this case needs to send a saml request to company 2 along with the following attributes such as first name, last name, email & few custom attributes. I have also gone through the omniauth-saml link but didn't see any example of how to send parameters using request_attributes. – Aniket Tiwari Sep 15 '21 at 07:00
  • uff thats a very specific requirement. If you control both Website 1 + 2, then a simple redirect/custom scheme would be fine. It totally depends on Website 1 (xyz.com in your example) to implement such functionality - I do not know that SAML itself supports internal "proxy" authentication by itself. Why would website 1 as a SP itself should redirect/post back the user info to you? I am not sure it is possible, but maybe update your question with a detailed flow that you imagine could happen. – stwienert Sep 15 '21 at 07:08
  • So to rephrase: there are 3 parties: a SAML Idp (OneLogin?), a SAML SP (Website 1, not under your control), and another SAML SP(your Website). SAML protocol does not allow you (to my knowledge) to ask SP1 for a user. If required, Website 1 would need to integrate specifically with your SP2 on a custom protocol (e.g. -> website1.com/auth/website2?username=abc -> SAML -> user comes back to w1 -> POST back to website2 with saml validated info....) – stwienert Sep 15 '21 at 07:10
  • No, I don't have control of the website 1. And there are only 2 parties website 1 and website 2. Website 1 is controlled by a different set of people. And website 2 is controlled by me, The requirement is to website 2 will implement a button and on click of the button, a saml request will be send to the website 1. Now website 1 will verify the saml request and along with it they also need some custom attributes which I have mentioned in above comment – Aniket Tiwari Sep 15 '21 at 07:23
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/237112/discussion-between-stwienert-and-aniket-shivam-tiwari). – stwienert Sep 15 '21 at 07:36