### Fetch SP Metadata with curl Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Example of fetching the SP's SAML metadata XML using curl. The output is an EntityDescriptor containing SP information. ```bash # curl https://myapp.example.com/users/saml/metadata ``` -------------------------------- ### Add devise_saml_authenticatable Gem Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Include the gem in your Gemfile and run bundle install to add SAML authentication capabilities. ```ruby gem 'devise_saml_authenticatable' ``` ```ruby bundle install ``` -------------------------------- ### SP Metadata Endpoint Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Renders the SP's SAML metadata XML. Accepts an optional `idp_entity_id` parameter for multi-IdP setups. ```http GET /users/saml/metadata GET /users/saml/metadata?idp_entity_id=https%3A%2F%2Fidp.example.com%2Fsaml ``` -------------------------------- ### Custom Entity ID Reader for SAML Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Replace the default IdP entity ID reader with a custom class that implements `.entity_id(params)`. This example shows a reader that prioritizes an `entity_id` query parameter before falling back to parsing SAML parameters. ```ruby class OurEntityIdReader def self.entity_id(params) if params[:entity_id].present? params[:entity_id] else DeviseSamlAuthenticatable::DefaultIdpEntityIdReader.entity_id(params) end end end ``` ```ruby Devise.setup do |config| config.idp_entity_id_reader = "OurEntityIdReader" end ``` ```text # GET /users/saml/sign_in?entity_id=https%3A%2F%2Fidp.tenant.com%2Fsaml ``` -------------------------------- ### Multi-Identity Provider Support with IdPSettingsAdapter Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Implement a custom IdPSettingsAdapter to support multiple Identity Providers. This adapter's `.settings` method should return a hash of IdP-specific settings, merging with base SAML configurations. ```ruby Devise.setup do |config| config.idp_settings_adapter = "IdPSettingsAdapter" config.idp_entity_id_reader = "OurEntityIdReader" config.saml_configure do |settings| settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" end end ``` ```ruby class IdPSettingsAdapter def self.settings(idp_entity_id, request) tenant = Organization.find_by_idp_entity_id(idp_entity_id) return {} unless tenant&.idp_setup idp = tenant.idp_setup { assertion_consumer_service_url: "#{request.protocol}#{request.host_with_port}/users/saml/auth", sp_entity_id: "#{request.protocol}#{request.host_with_port}/saml/metadata", idp_entity_id: idp.idp_entity_id, idp_sso_service_url: idp.idp_sso_service_url, idp_slo_service_url: idp.idp_slo_service_url, idp_cert: idp.idp_cert, } end end ``` -------------------------------- ### Configure IdP Certificate Fingerprint and Algorithm Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Set the IdP's certificate fingerprint and the algorithm used for fingerprint verification. Ensure these values match the IdP's configuration for successful authentication. ```ruby settings.idp_cert_fingerprint = "00:A1:2B:3C:44:55:6F:A7:88:CC:DD:EE:22:33:44:55:D6:77:8F:99" settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha1" end end ``` -------------------------------- ### Configure SAML IdP Settings Adapter Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Specify a custom adapter class for dynamically retrieving IdP settings, useful for supporting multiple Identity Providers. ```ruby config.idp_settings_adapter = "MyIdPSettingsAdapter" ``` -------------------------------- ### IdPSettingsAdapter for SAML Settings Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Define a custom adapter to dynamically fetch SAML settings based on the IdP entity ID. Ensure `assertion_consumer_service_url` and `issuer` are correctly configured for your production environment. ```ruby class IdPSettingsAdapter def self.settings(idp_entity_id) # Find your settings. Here we identify our tenant (Organization class) tenant = IdpSetup.find_by_idp_entity_id(idp_entity_id).organization # Urls below are for a development enviroment if tenant.present? { assertion_consumer_service_url: "http://#{tenant.short_name.parameterize}.localhost:3000/auth", assertion_consumer_service_binding: tenant.idp_setup.assertion_consumer_service_binding.present? ? tenant.idp_setup.assertion_consumer_service_binding : "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", name_identifier_format: tenant.idp_setup.name_identifier_format.present? ? tenant.idp_setup.name_identifier_format : "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", issuer: "http://#{tenant.short_name.parameterize}.localhost:3000/metadata", authn_context: "", idp_slo_target_url: "", idp_sso_target_url: tenant.idp_setup.idp_sso_target_url, idp_cert_fingerprint: tenant.idp_setup.idp_cert_fingerprint, idp_cert_fingerprint_algorithm: tenant.idp_setup.idp_cert_fingerprint_algorithm.present? ? tenant.idp_setup.idp_cert_fingerprint_algorithm : 'http://www.w3.org/2000/09/xmldsig#sha256' } else {} end end end ``` -------------------------------- ### Configure Core SAML Settings in Devise Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Set up SAML-specific configurations within the `Devise.setup` block, including user creation, attribute updates, and core ruby-saml settings. ```ruby # config/initializers/devise.rb Devise.setup do |config| # Whether to create a new user record when one cannot be found (default: false) config.saml_create_user = true # Whether to update user attributes on every successful login (default: false) config.saml_update_user = true # The model attribute used to look up the user (must be present in the SAML response) config.saml_default_user_key = :email # Use the SAML Subject (NameID) as the lookup value instead of an attribute config.saml_use_subject = true # Session key that stores the SAML session index (required for IdP-initiated SLO) config.saml_session_index_key = :saml_session_index # Tolerance for clock skew between SP and IdP (in seconds) config.allowed_clock_drift_in_seconds = 3 # Validate that SAMLResponse InResponseTo matches the original AuthnRequest ID config.saml_validate_in_response_to = true # Core ruby-saml settings config.saml_configure do |settings| settings.assertion_consumer_service_url = "https://myapp.example.com/users/saml/auth" settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" settings.sp_entity_id = "https://myapp.example.com/saml/metadata" settings.authn_context = "" settings.idp_entity_id = "https://idp.example.com/saml" settings.idp_sso_service_url = "https://idp.example.com/saml/sso" settings.idp_slo_service_url = "https://idp.example.com/saml/slo" # Use idp_cert for the full certificate (preferred) or fingerprint: settings.idp_cert = "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" # settings.idp_cert_fingerprint = "AB:CD:EF:..." # settings.idp_cert_fingerprint_algorithm = "http://www.w3.org/2000/09/xmldsig#sha256" end end ``` -------------------------------- ### Custom IdP Settings Adapter (Ruby) Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Implement a custom settings adapter class with a `#settings` method to support multiple Identity Providers and dynamic application domains. This method takes the IdP entity ID and request object to return IdP-specific settings. ```ruby class IdPSettingsAdapter def self.settings(idp_entity_id, request) case idp_entity_id when "http://www.example_idp_entity_id.com" { assertion_consumer_service_url: "#{request.protocol}#{request.host_with_port}/users/saml/auth", assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", sp_entity_id: "#{request.protocol}#{request.host_with_port}/saml/metadata", idp_entity_id: "http://www.example_idp_entity_id.com", authn_context: "", idp_slo_service_url: "http://example_idp_slo_service_url.com", idp_sso_service_url: "http://example_idp_sso_service_url.com", idp_cert: "example_idp_cert" } when "http://www.another_idp_entity_id.biz" { assertion_consumer_service_url: "http://localhost:3000/users/saml/auth", assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST", name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient", sp_entity_id: "http://localhost:3000/saml/metadata", idp_entity_id: "http://www.another_idp_entity_id.biz", authn_context: "", idp_slo_service_url: "http://another_idp_slo_service_url.com", idp_sso_service_url: "http://another_idp_sso_service_url.com", idp_cert: "another_idp_cert" } else {} end end end ``` -------------------------------- ### Custom Logout Action for Multiple Strategies Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Supporting-multiple-authentication-strategies Implement a custom `sign_out` action to handle SAML logout by checking the winning strategy and redirecting appropriately. ```ruby class AuthController def sign_out saml_sign_out = authenticated_with_saml? sign_out if saml_sign_out redirect_to destroy_saml_user_session_path else redirect_to root_path end end private def authenticated_with_saml? warden.session(:user)[:strategy] == :saml_authenticatable rescue Warden::NotAuthenticated false end end ``` -------------------------------- ### File-Based IdP Configuration (YAML) Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Configure Identity Provider (IdP) settings using a YAML file (`config/idp.yml`) which takes precedence over `saml_configure` block settings. The file is keyed by Rails environment. ```yaml # config/idp.yml production: assertion_consumer_service_url: "https://myapp.example.com/users/saml/auth" assertion_consumer_service_binding: "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" name_identifier_format: "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" sp_entity_id: "https://myapp.example.com/saml/metadata" idp_entity_id: "https://idp.example.com/saml" idp_sso_service_url: "https://idp.example.com/saml/sso" idp_slo_service_url: "https://idp.example.com/saml/slo" idp_cert: "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----" development: assertion_consumer_service_url: "http://localhost:3000/users/saml/auth" sp_entity_id: "http://localhost:3000/saml/metadata" idp_sso_service_url: "http://localhost/simplesaml/saml2/idp/SSOService.php" idp_slo_service_url: "http://localhost/simplesaml/saml2/idp/SingleLogoutService.php" idp_cert_fingerprint: "00:A1:2B:3C:44:55:6F:A7:88:CC:DD:EE:22:33:44:55:D6:77:8F:99" idp_cert_fingerprint_algorithm: "http://www.w3.org/2000/09/xmldsig#sha1" ``` -------------------------------- ### Configure SAML Settings in devise.rb Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Azure-AD Configure SAML settings in the devise.rb initializer file. Ensure all required URLs, identifiers, and certificates are correctly set. ```ruby config.saml_create_user = true config.saml_update_user = true config.saml_default_user_key = :email config.saml_session_index_key = :session_index config.saml_route_helper_prefix = 'saml' config.saml_configure do |settings| settings.assertion_consumer_service_url = Reply URL (Assertion Consumer Service URL) settings.issuer = Identifier (Entity ID) settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' settings.name_identifier_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:transient' settings.authn_context = 'urn:oasis:names:tc:SAML:2.0:ac:classes:Password' settings.idp_cert_fingerprint = Fingerprint generated from point c settings.idp_cert_fingerprint_algorithm = Signing algorithm (SAML Signing Certificate) settings.idp_slo_target_url = 'https://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php' settings.idp_entity_id = Login URL settings.idp_sso_target_url = Azure AD Identifier end ``` -------------------------------- ### Configure Devise for SAML Authentication Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Set up the devise initializer with SAML-specific configurations, including user creation/update policies, default user key, session index key, and the IDP settings adapter. ```ruby require 'id_p_settings_adapter' Devise.setup do |config| config.saml_create_user = true config.saml_update_user = true config.saml_default_user_key = :email config.saml_session_index_key = :session_index config.saml_use_subject = true config.idp_settings_adapter = IdPSettingsAdapter config.saml_configure do |settings| settings.assertion_consumer_service_url = "" settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" settings.issuer = "" settings.authn_context = "" settings.idp_slo_target_url = "" settings.idp_sso_target_url = "" settings.idp_cert_fingerprint = '' settings.idp_cert_fingerprint_algorithm = 'http://www.w3.org/2000/09/xmldsig#sha256' end end ``` -------------------------------- ### Configure SAML Identity Provider Settings Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Set essential SAML IdP and SP endpoints, bindings, and name identifier formats. This configuration is crucial for establishing the SAML communication channel. ```ruby config.saml_configure do |settings| settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth" settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" settings.name_identifier_format = "urn:oasis:names:tc:SAML:2.0:nameid-format:transient" settings.sp_entity_id = "http://localhost:3000/saml/metadata" settings.authn_context = "" settings.idp_slo_service_url = "http://localhost/simplesaml/www/saml2/idp/SingleLogoutService.php" settings.idp_sso_service_url = "http://localhost/simplesaml/www/saml2/idp/SSOService.php" end ``` -------------------------------- ### Configure SAML Session Index Key Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Optionally set a key to store the session index from the IdP, which can be used for IdP-initiated logout requests. ```ruby config.saml_session_index_key = :saml_session_index ``` -------------------------------- ### Define SAML Routes in Rails Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Add routes for SAML authentication endpoints, including sign-in, authentication callback, metadata, and single logout. ```ruby # config/routes.rb Rails.application.routes.draw do devise_for :users # Generated routes: # GET /users/saml/sign_in -> new (redirects to IdP) # POST /users/saml/auth -> create (consumes SAMLResponse) # GET /users/saml/metadata -> metadata # GET|POST /users/saml/idp_sign_out -> idp_sign_out (SLO) root to: "home#index" end ``` -------------------------------- ### Configure SAML Attribute Map Resolver (Ruby) Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Set up a custom attribute map resolver in an initializer to dynamically load attribute mappings based on the SAML response. This allows for different mappings per IdP. ```ruby # config/initializers/devise.rb Devise.setup do |config| ... # ==> Configuration for :saml_authenticatable config.saml_attribute_map_resolver = "MyAttributeMapResolver" end ``` -------------------------------- ### Define SAML Resource Locator Logic Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Implement a lambda to find the correct user record based on SAML response data. This is crucial for mapping SAML assertions to existing users. ```ruby config.saml_resource_locator = -> (model, saml_response, auth_value) { model.find_by(Devise.saml_default_user_key => auth_value) } ``` -------------------------------- ### Failed Authentication Handler Configuration Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Configure a custom handler for failed SAML authentication attempts. This allows for custom error reporting or redirects when authentication fails. ```ruby # A class with a #handle(saml_response, strategy) instance method invoked when SAML authentication fails (invalid response, user not found, validation rejected). Allows custom redirects or error reporting. ``` -------------------------------- ### Map SAML Attributes to User Fields (YAML) Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Define SAML attribute mappings in a YAML file. This is useful when you have a single IdP and a straightforward attribute mapping. ```yaml # attribute-map.yml "urn:mace:dir:attribute-def:uid": "user_name" "urn:mace:dir:attribute-def:email": "email" "urn:mace:dir:attribute-def:name": "last_name" "urn:mace:dir:attribute-def:givenName": "name" ``` -------------------------------- ### Configure SAML Route Helper Prefix Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Supporting-multiple-authentication-strategies Set the `saml_route_helper_prefix` in `config/initializers/devise.rb` to prefix SAML routes. ```ruby Devise.setup do |config| config.saml_route_helper_prefix = 'saml' end ``` -------------------------------- ### SP-Initiated Login with RelayState Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Configure Devise SAML to attach a RelayState to authentication requests, allowing users to be redirected to their originally intended page after successful login. This can be customized by subclassing the controller. ```ruby Devise.setup do |config| config.saml_relay_state = ->(request) { request.env["omniauth.origin"] || request.referer } end ``` ```ruby class SamlSessionsController < Devise::SamlSessionsController def new super end def create super end end ``` ```ruby devise_for :users, controllers: { saml_sessions: "saml_sessions" } ``` -------------------------------- ### Route Helper Prefix for Multiple Strategies Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Set a prefix for SAML-related route helpers to avoid naming collisions when using multiple Devise authentication strategies. This ensures unique route names like `new_saml_user_session_path`. ```ruby Devise.setup do |config| config.saml_route_helper_prefix = 'saml' # New route names: new_saml_user_session_path, destroy_saml_user_session_path, etc. end ``` -------------------------------- ### Custom SamlSessionsController for SAML Flow Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Implement a custom `SamlSessionsController` to handle the SAML authentication flow, including generating the SAML request and storing the authentication strategy. ```ruby class SamlSessionsController < Devise::SamlSessionsController after_filter :store_winning_strategy, only: :create def new request = OneLogin::RubySaml::Authrequest.new action = request.create(saml_config(current_tenant.idp_setup.idp_entity_id)) redirect_to action end def create if current_user.type.blank? current_user.update(type: "SampleUser") end super end private def store_winning_strategy warden.session(:user)[:strategy] = warden.winning_strategies[:user].class.name.demodulize.underscore.to_sym end end ``` -------------------------------- ### Configure SAML User Creation and Updates Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Enable or disable automatic user creation and attribute updates based on SAML responses. These can also be controlled by custom procs for conditional logic. ```ruby config.saml_create_user = true ``` ```ruby config.saml_update_user = true ``` -------------------------------- ### Core Authentication Hooks Configuration Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Customize user authentication by configuring custom resource locator, update hooks, and conditional user creation/update logic. ```ruby # config/initializers/devise.rb — customising all hooks Devise.setup do |config| # Custom resource locator: find user by a non-default key config.saml_resource_locator = ->(model, saml_response, auth_value) { model.find_by(sso_email: auth_value) || model.find_by(email: auth_value) } # Custom update hook: map attributes and assign a default role for new users config.saml_update_resource_hook = ->(user, saml_response, auth_value) { saml_response.attributes.resource_keys.each do |key| user.public_send("#{key}=", saml_response.attribute_value_by_resource_key(key)) end user.email = auth_value if Devise.saml_use_subject user.role ||= "member" # default role for newly created accounts user.save! } # Conditional user creation: only create users for the Admin model config.saml_create_user = Proc.new do |model_class, saml_response, auth_value| model_class == Admin end # Conditional user update config.saml_update_user = Proc.new do |model_class, saml_response, auth_value| model_class == User end end ``` -------------------------------- ### Attribute Mapping Configuration Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Okta Configure attribute mapping in `attribute-map.yml` to specify how SAML attributes correspond to user attributes. ```yaml # "urn:mace:dir:attribute-def:uid": "user_name" # "urn:mace:dir:attribute-def:email": "email" "last_name": "last_name" "first_name": "first_name" ``` -------------------------------- ### IdP-Initiated Single Logout Configuration Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Configure Devise for IdP-initiated Single Logout (SLO) by setting `saml_session_index_key` and `saml_sign_out_success_url`. ```ruby # config/initializers/devise.rb Devise.setup do |config| config.saml_session_index_key = :saml_session_index # After a successful SP-initiated logout, redirect here instead of the sign-in page: config.saml_sign_out_success_url = "https://myapp.example.com/goodbye" end ``` -------------------------------- ### Configure Devise Model for SAML Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Include the `:saml_authenticatable` strategy in your Devise model. Ensure the model has necessary attributes like email. ```ruby # app/models/user.rb class User < ActiveRecord::Base devise :saml_authenticatable, :trackable end ``` -------------------------------- ### Allow Clock Drift in SAML Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Configure the maximum allowed clock drift in seconds between the Service Provider and Identity Provider to account for time synchronization issues. ```ruby config.allowed_clock_drift_in_seconds = 0 ``` -------------------------------- ### Devise SAML Configuration Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Google-Apps Configures Devise to use SAML authentication with Google Apps. Requires the `ruby-saml` gem. Ensure `assertion_consumer_service_url` is set correctly and `idp_sso_target_url` points to your Google Apps SAML URL. ```ruby server_url = 'https://YOUR_SERVER_URL.com' config.saml_configure do |settings| # assertion_consumer_service_url is required starting with ruby-saml 1.4.3: https://github.com/onelogin/ruby-saml#updating-from-142-to-143 settings.assertion_consumer_service_url = "#{server_url}/users/saml/auth" settings.assertion_consumer_service_binding = "urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" settings.name_identifier_format = "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress" settings.issuer = "#{server_url}/users/saml/metadata" settings.authn_context = "" settings.idp_slo_target_url = "" # similar to https://accounts.google.com/o/saml2/idp?idpid=xxxxxx settings.idp_sso_target_url = "YOUR_GOOGLE_APPS_URL" settings.idp_cert = <<-CERT.chomp -----BEGIN CERTIFICATE----- ... -----END CERTIFICATE----- CERT end ``` -------------------------------- ### Failed Authentication Handler Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt A class with a `#handle(saml_response, strategy)` instance method invoked when SAML authentication fails (invalid response, user not found, validation rejected). Allows custom redirects or error reporting. ```APIDOC ## `Devise.saml_failed_callback` ### Description This configuration option specifies a class responsible for handling SAML authentication failures. When authentication fails due to an invalid response, user not found, or validation rejection, an instance of this class is created and its `#handle` method is called. ### Method Signature `#handle(saml_response, strategy)` ### Parameters - **`saml_response`** (Object) - The SAML response object associated with the failed authentication attempt. - **`strategy`** (Object) - The Warden strategy object. ### Purpose Allows for custom error reporting, logging, or redirecting users to specific pages upon SAML authentication failure. ``` -------------------------------- ### Custom SAML Failure Callback Handler Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Implement a custom handler for SAML authentication failures. This allows for specific error logging and redirection to a custom error page instead of the default sign-in page. ```ruby class SamlFailedCallbackHandler def handle(saml_response, strategy) errors = saml_response.errors.join(", ") Rails.logger.error("SAML auth failure: #{errors}") # Redirect the browser to a custom error page instead of the default sign-in strategy.redirect!("https://myapp.example.com/sso_error?reason=auth_failed") end end ``` ```ruby Devise.setup do |config| config.saml_failed_callback = "SamlFailedCallbackHandler" end ``` -------------------------------- ### Custom Devise Failure App for SAML Redirects Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Redirect-loop Implement a custom failure app to handle specific redirect scenarios, such as when the attempted path is the SAML user session path. This prevents infinite redirect loops by redirecting to a specific failure page instead of defaulting to the superclass behavior. ```ruby def respond if attempted_path == saml_user_session_path redirect_to whatever_saml_sso_failure_path else super end end ``` -------------------------------- ### Add devise_saml_authenticatable to Gemfile Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Add the gem to your application's Gemfile to include it in your project. ```ruby # Gemfile gem "devise_saml_authenticatable" ``` -------------------------------- ### SP Metadata Endpoint Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Renders the SP's SAML metadata XML (entity descriptor) that IdPs need to configure the SP. Accepts an optional `idp_entity_id` parameter when using the IdP settings adapter. ```APIDOC ## GET /users/saml/metadata ### Description Provides the Service Provider's SAML metadata XML. ### Method GET ### Endpoint /users/saml/metadata ### Query Parameters - **idp_entity_id** (string) - Optional - The entity ID of the Identity Provider, used when the IdP settings adapter is configured. ### Response Example #### Success Response (200) - **XML** - SAML Entity Descriptor XML ```xml ``` ``` -------------------------------- ### Auth0 SAML2 Web App Addon Settings Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Auth0 Configure these settings within the SAML2 web app addon of your Auth0 application. These define how Auth0 identifies users in SAML assertions. ```json { "nameIdentifierFormat": "urn:oasis:names:tc:SAML:1.1:nameid-format:emailAddress", "nameIdentifierProbes": [ "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress" ] } ``` -------------------------------- ### Custom SAML Routes with Devise Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Override default Devise routes to enable a custom `SamlSessionsController` and manually define SAML-specific routes to avoid conflicts. ```ruby ... devise_for :users, skip: [:registrations, :saml_authenticatable], controllers: { sessions: 'user/sessions' } as :user do get 'users/edit' => 'devise_registrations#edit', as: 'edit_registration' patch 'users' => 'devise_registrations#update', as: 'registration' # To avoid conflict, saml_authenticatable routes are manually defined here. # Also we override the controller. resource :session, as: 'saml_session', only: [], controller: 'saml_sessions', path: '/' do get :new, path: 'sign_in', as: 'new' match :destroy, path: 'sign_out', as: 'destroy', via: :get post :create, path: 'auth' get :metadata, path: 'metadata' match :idp_sign_out, path: 'idp_sign_out', via: [:get, :post] end end ... ``` -------------------------------- ### Define SAML Attribute Mappings Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Setting-Up-with-Multiple-Providers Map SAML attributes from the Identity Provider (IdP) to your User model's columns. Ensure required attributes like 'email' are configured. ```yaml "email": "email" # key is the IdP name and value is the column it is mapped to in your application "lastName": "last_name" "firstName": "first_name" ``` -------------------------------- ### Configure Devise SAML Settings Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/OneLogin Use this block to configure SAML settings within your Devise initializer. Ensure all placeholder values are replaced with your specific OneLogin details. ```ruby config.saml_configure do |settings| settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth" settings.issuer = "http://localhost:3000/users/saml/metadata" settings.idp_entity_id = "" settings.idp_slo_target_url = "" settings.idp_sso_target_url = "" settings.idp_cert_fingerprint = "" end ``` -------------------------------- ### Configure Model for SAML Authentication Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Add `:saml_authenticatable` to your Devise model's configuration to enable SAML authentication. Ensure other necessary Devise strategies like `:trackable` are also included. ```ruby class User < ActiveRecord::Base ... devise :saml_authenticatable, :trackable ... end ``` -------------------------------- ### Define SAML Failed Callback Handler Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Specify a custom handler class to manage responses to failed SAML authentication attempts, allowing for custom error handling or redirects. ```ruby config.saml_failed_callback = "MySamlFailedCallbacksHandler" ``` -------------------------------- ### Draw User Routes Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Supporting-multiple-authentication-strategies Draw routes for users normally using `devise_for`. ```ruby devise_for :users ``` -------------------------------- ### Custom Attribute Map Resolver for SAML Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Subclass DefaultAttributeMapResolver to dynamically provide attribute maps based on the IdP issuer from the SAML response. Falls back to the default configuration if no specific issuer is matched. ```ruby class MyAttributeMapResolver < DeviseSamlAuthenticatable::DefaultAttributeMapResolver def attribute_map issuer = saml_response&.issuers&.first case issuer when "https://idp-a.example.com/saml" { "urn:mace:dir:attribute-def:email" => "email", "urn:mace:dir:attribute-def:givenName" => "first_name", "urn:mace:dir:attribute-def:sn" => "last_name", } when "https://idp-b.example.com/saml" { "emailAddress" => "email", "firstName" => "first_name", "lastName" => "last_name", } else super # falls back to config/attribute-map.yml end end end ``` ```ruby Devise.setup do |config| config.saml_attribute_map_resolver = "MyAttributeMapResolver" end ``` -------------------------------- ### Customize SAML Resource Update Logic Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Define a lambda hook to specify how user attributes are updated from the SAML response. This hook is called when `saml_create_user` or `saml_update_user` are enabled. ```ruby config.saml_update_resource_hook = -> (user, saml_response, auth_value) { saml_response.attributes.resource_keys.each do |key| user.send "#{key}=", saml_response.attribute_value_by_resource_key(key) end if (Devise.saml_use_subject) user.send "#{Devise.saml_default_user_key}=", auth_value end user.save! } ``` -------------------------------- ### Customize SAML Route Helper Prefix Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Set a prefix for Devise SAML-generated named routes to avoid collisions with other gems or modules. ```ruby config.saml_route_helper_prefix = 'saml' ``` -------------------------------- ### Configure SAML Attribute Mapping Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Define the mapping between SAML assertion attribute names and your User model's column names in `config/attribute-map.yml`. Supports environment-specific configurations. ```yaml # config/attribute-map.yml # Flat mapping (applies to all environments): "urn:mace:dir:attribute-def:uid": "user_name" "urn:mace:dir:attribute-def:email": "email" "urn:mace:dir:attribute-def:sn": "last_name" "urn:mace:dir:attribute-def:givenName": "first_name" # --- OR --- environment-specific mapping: # production: # "email": "email" # "lastName": "last_name" # development: # "email": "email" # "lastName": "last_name" ``` -------------------------------- ### Smart Logout Routing with SAML Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Implement custom logout logic to differentiate between SAML and other authentication strategies. This ensures that SAML Single Logout (SLO) is triggered correctly when the user was authenticated via SAML. ```ruby class AuthController < ApplicationController def sign_out saml_sign_out = authenticated_with_saml? sign_out # invalidate local session regardless if saml_sign_out redirect_to destroy_saml_user_session_path # triggers IdP SLO else redirect_to root_path end end private def authenticated_with_saml? warden.session(:user)[:strategy] == :saml_authenticatable rescue Warden::NotAuthenticated false end end ``` -------------------------------- ### Custom Attribute Map Resolver Implementation (Ruby) Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Implement a custom attribute map resolver class that inherits from `DeviseSamlAuthenticatable::DefaultAttributeMapResolver`. Override the `attribute_map` method to provide specific mappings based on the IdP issuer. ```ruby # app/lib/my_attribute_map_resolver class MyAttributeMapResolver < DeviseSamlAuthenticatable::DefaultAttributeMapResolver def attribute_map issuer = saml_response.issuers.first case issuer when "idp_entity_id" { "urn:mace:dir:attribute-def:uid" => "user_name", "urn:mace:dir:attribute-def:email" => "email", "urn:mace:dir:attribute-def:name" => "last_name", "urn:mace:dir:attribute-def:givenName" => "name", } end end end ``` -------------------------------- ### Use SAML Subject for User Identification Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Determine whether to use the SAML Subject or assertion attributes for identifying the user. Defaults to using attributes. ```ruby config.saml_use_subject = true ``` -------------------------------- ### Set Default User Key for SAML Lookup Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Specify the attribute used to look up users in your database during SAML authentication. Ensure this attribute is present in the SAML response. ```ruby config.saml_default_user_key = :email ``` -------------------------------- ### Attribute Mapping for Google SAML Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Google-Apps Maps SAML attributes from Google to Devise attributes. Ensure these values match the Attribute Mapping section in your Google SAML app configuration. ```yaml all: first_name: "first_name" last_name: "last_name" primary_email: "email" ``` -------------------------------- ### Customize IdP Entity ID Reader Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Set a custom class to read the IdP entity ID from SAML messages, enabling support for multiple IdPs. ```ruby config.idp_entity_id_reader = "DeviseSamlAuthenticatable::DefaultIdpEntityIdReader" ``` -------------------------------- ### Devise SAML Configuration Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Auth0 Configure Devise to use Auth0 as a SAML Identity Provider. Ensure the `assertion_consumer_service_url` matches your application's endpoint and replace placeholders for Auth0 details. ```ruby # config/initializer/devise.rb snippet # assuming your user model is User... config.saml_configure do |settings| settings.assertion_consumer_service_url = "http://localhost:3000/users/saml/auth" settings.assertion_consumer_service_binding = 'urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST' settings.name_identifier_format = 'urn:oasis:names:tc:SAML:2.0:nameid-format:emailAddress' settings.issuer = "http://localhost:3000/users/saml/metadata" settings.authn_context = '' # NOTE: set the idp_slo_target_url if you need to support Single Logout settings.idp_slo_target_url = '' settings.idp_sso_target_url = 'https://[your auth0 account].auth0.com/samlp/[your auth0 application client ID]' settings.idp_cert_fingerprint = '[Certificate Fingerprint; see above]' settings.idp_cert_fingerprint_algorithm = 'http://www.w3.org/2000/09/xmldsig#sha1' end ``` -------------------------------- ### Map SAML Attributes in attribute-map.yml Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Azure-AD Map SAML attributes from Azure AD to your application's user model attributes in the attribute-map.yml file. This ensures user data is correctly imported or updated. ```yaml "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname": "last_name" "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname": "first_name" "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress": "email" ``` -------------------------------- ### IdP-Initiated Single Logout Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Handles inbound SLO `LogoutRequest` messages from the IdP, invalidates the local session, and sends a `LogoutResponse` back to the IdP. Also handles `LogoutResponse` messages (the reply to an SP-initiated logout). ```APIDOC ## POST /users/saml/idp_sign_out ### Description Handles SAML Single Logout requests and responses initiated by the Identity Provider. ### Method POST (also supports GET with SAMLRequest parameter) ### Endpoint /users/saml/idp_sign_out ### Description This endpoint processes incoming SAML `LogoutRequest` messages from an IdP. It invalidates the local user session and sends a `LogoutResponse` back to the IdP. It also handles `LogoutResponse` messages that are replies to SP-initiated logouts. ``` -------------------------------- ### Core Authentication Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt The class-level method called by the Warden strategy to resolve or create the User record from a validated SAML response. Applies custom locator, validator, create, and update hooks. ```APIDOC ## `Devise::Models::SamlAuthenticatable.authenticate_with_saml` ### Description This class-level method is invoked by the Warden strategy to find or create a User record based on a validated SAML response. It allows for customization through various hooks. ### Configuration Hooks - **`saml_resource_locator`**: A proc to find a user record using a non-default key. Receives `(model, saml_response, auth_value)`. - **`saml_update_resource_hook`**: A proc to map SAML attributes to user attributes and perform other updates (e.g., assign default roles) for existing or newly created users. Receives `(user, saml_response, auth_value)`. - **`saml_create_user`**: A proc to conditionally control user creation. Receives `(model_class, saml_response, auth_value)` and should return `true` to allow creation. - **`saml_update_user`**: A proc to conditionally control user updates. Receives `(model_class, saml_response, auth_value)` and should return `true` to allow updates. ``` -------------------------------- ### Custom Authentication Validation Hook Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Implement a custom validation hook to reject SAML responses based on business logic, such as user suspension or IdP group membership. ```ruby # config/initializers/devise.rb Devise.setup do |config| # saml_resource_validator_hook receives (user_or_nil, decorated_saml_response, auth_value) # Must return true to allow login, false to reject it. config.saml_resource_validator_hook = ->(user, saml_response, auth_value) { return false if user.nil? return false if user.suspended? # Only allow login from a known IdP group attribute allowed_groups = saml_response.attributes.value_by_resource_key("group") allowed_groups.to_s.include?("app-access") } end ``` -------------------------------- ### Format DER Certificate String in Python Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Getting-the-IDP's-X.509-Certificate-Fingerprint-and-Algorithm Use this Python script to reformat a DER-encoded certificate string into a PEM-like format. This is necessary before using online tools to generate the certificate's fingerprint. Ensure 'your DER string here' is replaced with the actual certificate data. ```python cert = 'your DER string here' headers = [ '-----BEGIN CERTIFICATE-----', '-----END CERTIFICATE-----' ] formatted = '\n'.join([headers[0]] + [cert[i:i+64] for i in range(0, len(cert), 64)] +[headers[1]]) print(formatted) ``` -------------------------------- ### Patch Devise SAML Controller for Strategy Tracking Source: https://github.com/apokalipto/devise_saml_authenticatable/wiki/Supporting-multiple-authentication-strategies Patch `Devise::SamlSessionsController` to add an `after_action` that stores the warden `winning_strategy`. ```ruby Devise::SamlSessionsController.class_eval do after_action :store_winning_strategy, only: :create private def store_winning_strategy warden.session(:user)[:strategy] = warden.winning_strategies[:user].class.name.demodulize.underscore.to_sym end end ``` -------------------------------- ### Validate SAML In-Response-To Header Source: https://github.com/apokalipto/devise_saml_authenticatable/blob/master/README.md Enable validation to ensure the SAML response's In-Response-To header matches the sent SAML request ID, enhancing security. ```ruby config.saml_validate_in_response_to = false ``` -------------------------------- ### Custom SAML Name Identifier for Logout Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Define a custom proc to retrieve the user identifier for SAML LogoutRequests. This is used when the IdP expects a specific format other than the default user key, such as a persistent UUID. ```ruby Devise.setup do |config| # Default: uses saml_default_user_key (e.g. user.email) # Override when the IdP expects a different identifier (e.g. a persistent UUID): config.saml_name_identifier_retriever = ->(current_user) { current_user.saml_persistent_id # a stored IdP-assigned identifier } end ``` -------------------------------- ### Store Winning Strategy in Devise SAML Sessions Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt Patch the Devise SAML sessions controller to store the winning authentication strategy after a successful login. This information is used by other parts of the application, such as custom logout logic. ```ruby Devise::SamlSessionsController.class_eval do after_action :store_winning_strategy, only: :create private def store_winning_strategy warden.session(:user)[:strategy] = warden.winning_strategies[:user].class.name.demodulize.underscore.to_sym end end ``` -------------------------------- ### Custom Authentication Validation Source: https://context7.com/apokalipto/devise_saml_authenticatable/llms.txt A proc called after the user record is located (or determined to be nil) that can reject a technically valid SAML response for business logic reasons. ```APIDOC ## `Devise.saml_resource_validator_hook` ### Description This hook allows for custom validation of a SAML response after a user record has been located (or determined to be `nil`). It can reject a technically valid SAML response based on business logic. ### Parameters - **`user_or_nil`** (User or nil) - The located user record, or `nil` if not found. - **`decorated_saml_response`** (Object) - The SAML response object, decorated for easier access to attributes. - **`auth_value`** (string) - The authentication value extracted from the SAML response. ### Return Value - `true` to allow login. - `false` to reject login. ```