Skip to content

Rails_Integration

Taketo Takashima edited this page Feb 5, 2021 · 3 revisions

How to integrate with Rails app

Add gem into your Gemfile

  gem 'saml_idp'

Add required routes for your route file

    get '/saml/metadata' => 'saml_idp#show'
    get '/saml/auth' => 'saml_idp#new'
    post '/saml/auth' => 'saml_idp#create'
    match '/saml/logout' => 'saml_idp#logout', via: [:get, :post, :delete]

Create your new controller for your SAML IdP feature

Following example is shows only shows that required methods you need to define in your controller. If you are Devise user please properly use Devise methods for authentication of your users.

Note: For Devise user please aware of that SAML request could be more that 4Kb if you are using cookie for your session storage. You might want to use Redis for your session storage or override Devise function store_location_for to store SAML request to different places.

  class SamlIdpController < ApplicationController
    include SamlIdp::Controller
    
    protect_from_forgery

    before_action :validate_saml_request, only: [:new, :create, :logout]

    def new
      render template: "saml_idp/idp/new"
    end

    def show
      render xml: SamlIdp.metadata.signed
    end

    def create
      unless params[:email].blank? && params[:password].blank?
        person = idp_authenticate(params[:email], params[:password])
        if person.nil?
          @saml_idp_fail_msg = "Incorrect email or password."
        else
          @saml_response = idp_make_saml_response(person)
          render :template => "saml_idp/idp/saml_post", :layout => false
          return
        end
      end
      render :template => "saml_idp/idp/new"
    end

    def logout
      idp_logout
      @saml_response = idp_make_saml_response(nil)
      render :template => "saml_idp/idp/saml_post", :layout => false
    end

    def idp_logout
      user = User.by_email(saml_request.name_id)
      user.logout
    end
    private :idp_logout

    def idp_authenticate(email, password)
      user = User.by_email(email).first
      user && user.valid_password?(password) ? user : nil
    end
    protected :idp_authenticate

    def idp_make_saml_response(person)
      # NOTE encryption is optional
      encode_response person, encryption: {
        cert: saml_request.service_provider.cert,
        block_encryption: 'aes256-cbc',
        key_transport: 'rsa-oaep-mgf1p'
      }
    end
    protected :idp_make_saml_response
  end

Create your view for GET request (SAML SP initiated request)

Following sample views are used as SAML GET request and mimic POST request for your SAML controller Or if you want to handle GET request in your new action with your own login you don't need to use form.

Without Devise gem you probably need to authenticate your user with your own login form.

# saml_idp/idp/new
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  </head>
  <body>
      <% if @saml_idp_fail_msg %>
        <div id="saml_idp_fail_msg" class="flash error"><%= @saml_idp_fail_msg %></div>
      <% end %>
      <%= form_tag do %>
        <%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
        <%= hidden_field_tag("RelayState", params[:RelayState]) %>
        <p>
          <%= label_tag :email %>
          <%= email_field_tag :email, params[:email], :autocapitalize => "off", :autocorrect => "off", :autofocus => "autofocus", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
        </p>
        <p>
          <%= label_tag :password %>
          <%= password_field_tag :password, params[:password], :autocapitalize => "off", :autocorrect => "off", :spellcheck => "false", :size => 30, :class => "email_pwd txt" %>
        </p>
        <p>
          <%= submit_tag "Sign in", :class => "button big blueish" %>
        </p>
      <% end %>
  </body>
</html>

If you are Devise user you following sample view will help to mimic GET request automatically as POST request.

# saml_idp/idp/new
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  </head>
  <body onload="document.forms[0].submit();" style="visibility:hidden;">
    <%= form_tag do %>
      <%= hidden_field_tag("SAMLRequest", params[:SAMLRequest]) %>
      <%= hidden_field_tag("RelayState", params[:RelayState]) %>
    <% end %>
  </body>
</html>

Create view for POST request (SAML Response)

Most important view for SAML IdP is following auto submit form that submit SAML response to SAML SP via browser.

# saml_idp/idp/saml_post
<!DOCTYPE html>
<html>
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
  </head>
  <body onload="document.forms[0].submit();" style="visibility:hidden;">
    <%= form_tag(saml_acs_url) do %>
      <%= hidden_field_tag("SAMLResponse", @saml_response) %>
      <%= hidden_field_tag("RelayState", params[:RelayState]) %>
      <%= submit_tag "Submit" %>
    <% end %>
  </body>
</html>

Security suggestion

  1. Never use sample public and private keys from this gem. Private key used for secure your SAML request and response. If you use publicly published private key your IdP service become door without lock.

  2. To implement security validation feature on your controller Most of common security validations are listed in OWASP SAML Security page. We would suggest to implement required one for your IdP. SAML Security Cheat Sheet Recommending to implement: "AuthnRequest(ID, SP)" from Validate Protocol Usage.

  3. We highly recommended that store your private key in very secure place such as KMS.