### 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.
```