Skip to main content
The SAMLServiceApi provides methods for handling SAML 2.0 authentication flows, including SAML request processing and response creation.

Initialize the API

Access the SAML Service through the Zitadel client:
require 'zitadel-client'

client = Zitadel::Client::Zitadel.with_access_token(
  "https://example.us1.zitadel.cloud",
  "your-access-token"
)

# Access SAML service
saml_service = client.saml

Methods

get_saml_request

Get SAML Request details by ID. Returns parsed details from the application’s SAML Request.
saml_request_id
string
required
The ID of the SAML request to retrieve
request = Zitadel::Client::SAMLServiceGetSAMLRequestRequest.new(
  saml_request_id: "saml_request_id_from_flow"
)

response = client.saml.get_saml_request(request)
puts "SAML Request ID: #{response.id}"
puts "Issuer: #{response.issuer}"
puts "ACS URL: #{response.acs_url}"
Required permissions: session.readReturns: Details about the SAML request including:
  • Request ID
  • Issuer (Service Provider Entity ID)
  • Assertion Consumer Service URL
  • Requested attributes
  • NameID format

create_response

Finalize a SAML Request and get the response definition for success or failure. The response must be handled per the SAML specification to inform the application.
saml_request_id
string
required
The ID of the SAML request to finalize
request = Zitadel::Client::SAMLServiceCreateResponseRequest.new(
  saml_request_id: "saml_request_id"
)

response = client.saml.create_response(request)
puts "SAML Response: #{response.saml_response}"
puts "Relay State: #{response.relay_state}"
Required permissions: session.link
This method can only be called once per SAML request. Subsequent calls will result in an error.
Returns: SAML response details including:
  • Base64-encoded SAMLResponse
  • RelayState (if provided in the original request)
  • POST binding information for the Service Provider

Common Use Cases

Processing SAML Authentication Request

# 1. Receive SAML request (typically via HTTP-Redirect or HTTP-POST)
# Extract the SAMLRequest parameter and decode it
saml_request_id = extract_saml_request_id(params[:SAMLRequest])

# 2. Get SAML request details
saml_request = client.saml.get_saml_request(
  Zitadel::Client::SAMLServiceGetSAMLRequestRequest.new(
    saml_request_id: saml_request_id
  )
)

# 3. Display authentication UI or validate existing session
# ... your authentication logic ...

# 4. Create SAML response after successful authentication
saml_response = client.saml.create_response(
  Zitadel::Client::SAMLServiceCreateResponseRequest.new(
    saml_request_id: saml_request_id
  )
)

# 5. POST the SAMLResponse back to the Service Provider
render_saml_post_form(
  acs_url: saml_request.acs_url,
  saml_response: saml_response.saml_response,
  relay_state: saml_response.relay_state
)

Building SAML POST Form

def render_saml_post_form(acs_url:, saml_response:, relay_state:)
  html = <<~HTML
    <!DOCTYPE html>
    <html>
    <head>
      <title>SAML POST</title>
    </head>
    <body onload="document.forms[0].submit()">
      <form method="post" action="#{acs_url}">
        <input type="hidden" name="SAMLResponse" value="#{saml_response}" />
        <input type="hidden" name="RelayState" value="#{relay_state}" />
        <noscript>
          <button type="submit">Continue</button>
        </noscript>
      </form>
    </body>
    </html>
  HTML
  
  render html: html.html_safe
end

Complete SAML SSO Flow

class SamlController < ApplicationController
  def sso
    # Receive SAML request
    saml_request_id = decode_saml_request(params[:SAMLRequest])
    
    # Get request details
    saml_request = client.saml.get_saml_request(
      Zitadel::Client::SAMLServiceGetSAMLRequestRequest.new(
        saml_request_id: saml_request_id
      )
    )
    
    # Check if user is authenticated
    if current_user
      finalize_saml_authentication(saml_request_id, saml_request)
    else
      # Store request ID and redirect to login
      session[:saml_request_id] = saml_request_id
      redirect_to login_path
    end
  end
  
  def callback
    # Called after successful authentication
    saml_request_id = session[:saml_request_id]
    
    # Create SAML response
    saml_response = client.saml.create_response(
      Zitadel::Client::SAMLServiceCreateResponseRequest.new(
        saml_request_id: saml_request_id
      )
    )
    
    # Get original request for ACS URL
    saml_request = client.saml.get_saml_request(
      Zitadel::Client::SAMLServiceGetSAMLRequestRequest.new(
        saml_request_id: saml_request_id
      )
    )
    
    # POST back to Service Provider
    render_saml_post_form(
      acs_url: saml_request.acs_url,
      saml_response: saml_response.saml_response,
      relay_state: saml_response.relay_state
    )
  end
end

Best Practices

Always validate the SAML request signature and issuer before processing authentication
SAML responses contain sensitive authentication data. Always transmit them over HTTPS and never log the full response content.
Store the SAML request ID in the user’s session to maintain state between the initial request and the authentication callback

Security Considerations

  1. Signature Validation: Always validate SAML request signatures when using signed requests
  2. HTTPS Only: SAML flows must use HTTPS to protect authentication data
  3. Issuer Validation: Verify the issuer matches an expected Service Provider
  4. Replay Protection: SAML requests should only be processed once
  5. Time Validation: Check NotBefore and NotOnOrAfter conditions

Error Handling

begin
  response = client.saml.create_response(request)
rescue Zitadel::Client::ApiError => e
  case e.code
  when 400
    # Invalid SAML request format
    render_saml_error("Invalid SAML request: #{e.message}")
  when 404
    # SAML request not found or already processed
    render_saml_error("SAML request not found or expired")
  when 403
    # Insufficient permissions
    render_saml_error("Insufficient permissions to process SAML request")
  else
    # Other errors
    Rails.logger.error("SAML error: #{e.message}")
    render_saml_error("An error occurred during SAML authentication")
  end
end

Common Error Scenarios

If you attempt to call create_response multiple times for the same SAML request, you’ll receive a 404 error. SAML requests can only be finalized once.Solution: Track which requests have been processed and prevent duplicate submissions.
SAML requests have a limited lifetime. Expired requests cannot be processed.Solution: Handle authentication promptly after receiving the SAML request. Consider implementing a timeout warning for users.
Passing an invalid or malformed SAML request ID will result in a 404 error.Solution: Validate the request ID format before making API calls and handle decoding errors gracefully.

SAML Bindings

Zitadel supports the following SAML bindings:
  • HTTP-Redirect: Used for sending SAML requests via URL parameters
  • HTTP-POST: Used for sending SAML responses via form POST
The SDK handles the authentication and response creation. Your application is responsible for:
  1. Receiving and decoding incoming SAML requests
  2. POSTing the SAML response back to the Service Provider

See Also

Build docs developers (and LLMs) love