Federated authentication systems in Ruby on Rails enable organizations to build secure, scalable identity management solutions. I’ve spent years implementing these systems across various enterprises, and I’ll share practical techniques that have proven effective.
Authentication across multiple applications requires careful consideration of security, user experience, and maintainability. The foundation starts with proper JWT (JSON Web Token) implementation.
module Authentication
class TokenManager
def self.generate(user)
payload = {
sub: user.id,
email: user.email,
roles: user.roles,
exp: 24.hours.from_now.to_i,
iat: Time.current.to_i
}
JWT.encode(payload, Rails.application.credentials.jwt_secret, 'HS256')
end
def self.verify(token)
JWT.decode(token, Rails.application.credentials.jwt_secret, true, algorithm: 'HS256')
rescue JWT::DecodeError
nil
end
end
end
Single Sign-On (SSO) implementation requires a centralized authentication service. Here’s how to create a robust SSO provider:
class SsoProvider
def initialize(client_id)
@client = OAuth2::Client.new(
client_id,
Rails.application.credentials.oauth_secret,
site: 'https://sso.example.com'
)
end
def authenticate(code)
token = @client.auth_code.get_token(code)
user_data = token.get('/api/v1/me').parsed
User.find_or_create_by(email: user_data['email']) do |user|
user.name = user_data['name']
user.roles = user_data['roles']
end
end
end
Session synchronization across multiple domains requires careful handling of cookies and tokens:
class SessionController < ApplicationController
def create
user = SsoProvider.new(params[:client_id]).authenticate(params[:code])
token = Authentication::TokenManager.generate(user)
domains.each do |domain|
cookies.signed[domain] = {
value: token,
httponly: true,
secure: Rails.env.production?,
domain: domain
}
end
redirect_to after_login_path
end
end
SAML integration provides enterprise-grade authentication capabilities:
class SamlController < ApplicationController
def init
request = OneLogin::RubySaml::Authrequest.new
redirect_to(request.create(saml_settings))
end
def callback
response = OneLogin::RubySaml::Response.new(
params[:SAMLResponse],
settings: saml_settings
)
if response.is_valid?
user = create_or_update_user(response.attributes)
sign_in(user)
redirect_to dashboard_path
else
redirect_to login_path, alert: 'Authentication failed'
end
end
private
def saml_settings
settings = OneLogin::RubySaml::Settings.new
settings.assertion_consumer_service_url = saml_callback_url
settings.issuer = "your-app-entity-id"
settings.idp_sso_target_url = "https://idp.example.com/saml2/sso"
settings.idp_cert_fingerprint = "your-idp-certificate-fingerprint"
settings
end
end
Multi-domain authentication requires careful consideration of CORS and security headers:
class ApplicationController < ActionController::Base
before_action :set_security_headers
private
def set_security_headers
response.headers['X-Frame-Options'] = 'SAMEORIGIN'
response.headers['X-XSS-Protection'] = '1; mode=block'
response.headers['X-Content-Type-Options'] = 'nosniff'
response.headers['Content-Security-Policy'] = content_security_policy
end
def content_security_policy
[
"default-src 'self'",
"connect-src 'self' #{allowed_domains.join(' ')}",
"frame-src #{allowed_domains.join(' ')}",
"img-src 'self' data: https:",
"script-src 'self' 'unsafe-inline'"
].join('; ')
end
end
Identity provider integration often requires handling various protocols:
module IdentityProvider
class Factory
def self.create(provider_name)
case provider_name
when 'google'
Google.new
when 'azure'
Azure.new
when 'okta'
Okta.new
else
raise "Unsupported provider: #{provider_name}"
end
end
end
class Base
def authenticate(credentials)
raise NotImplementedError
end
def refresh_token(token)
raise NotImplementedError
end
end
end
Secure token storage and management:
class TokenVault
def initialize(user)
@user = user
@redis = Redis.new
end
def store_token(token, expires_in: 24.hours)
key = "user:#{@user.id}:token"
@redis.setex(key, expires_in.to_i, encrypt_token(token))
end
def retrieve_token
key = "user:#{@user.id}:token"
encrypted_token = @redis.get(key)
decrypt_token(encrypted_token) if encrypted_token
end
private
def encrypt_token(token)
cipher = OpenSSL::Cipher.new('AES-256-GCM')
cipher.encrypt
cipher.key = encryption_key
iv = cipher.random_iv
encrypted = cipher.update(token) + cipher.final
auth_tag = cipher.auth_tag
Base64.strict_encode64([encrypted, iv, auth_tag].pack('m*m*m*'))
end
def decrypt_token(encrypted_data)
encrypted, iv, auth_tag = Base64.strict_decode64(encrypted_data)
.unpack('m*m*m*')
decipher = OpenSSL::Cipher.new('AES-256-GCM')
decipher.decrypt
decipher.key = encryption_key
decipher.iv = iv
decipher.auth_tag = auth_tag
decipher.update(encrypted) + decipher.final
end
end
Role-based access control integration:
module Authorization
class Policy
def initialize(user)
@user = user
@roles = user.roles
end
def can?(action, resource)
permissions = fetch_permissions_for_roles
permissions.any? do |permission|
permission.action == action &&
permission.resource == resource
end
end
private
def fetch_permissions_for_roles
Permission.where(role: @roles).includes(:role)
end
end
end
Error handling and logging for authentication failures:
module AuthenticationError
class Handler
def self.handle(error)
case error
when JWT::ExpiredSignature
log_error('Token expired', error)
:token_expired
when JWT::InvalidIssuerError
log_error('Invalid token issuer', error)
:invalid_issuer
when OAuth2::Error
log_error('OAuth error', error)
:oauth_error
else
log_error('Unknown authentication error', error)
:unknown_error
end
end
def self.log_error(message, error)
Rails.logger.error(
message: message,
error: error.class.name,
backtrace: error.backtrace&.first(5),
timestamp: Time.current
)
end
end
end
These implementations provide a solid foundation for building federated authentication systems in Ruby on Rails. Remember to regularly update dependencies, conduct security audits, and maintain proper documentation for your authentication system.