class OidcAuthController < ApplicationController allow_unauthenticated_access only: [:authorize, :callback] # POST /auth/oidc - Initiate OIDC flow def authorize redirect_to oidc_client.authorization_uri( scope: [:openid, :email, :profile], state: generate_state_token, nonce: generate_nonce ), allow_other_host: true end # GET /auth/oidc/callback - Handle provider callback def callback # Verify state token unless valid_state_token?(params[:state]) redirect_to new_session_path, alert: "Invalid authentication state" return end # Exchange authorization code for tokens oidc_client.authorization_code = params[:code] access_token = oidc_client.access_token! # Get user info user_info = access_token.userinfo! # Find user by email user = User.find_by(email_address: user_info.email) unless user redirect_to new_session_path, alert: "No user found with email: #{user_info.email}" return end # Update role based on OIDC groups if present if user_info.respond_to?(:groups) && user_info.groups.present? user.update_role_from_oidc_groups(user_info.groups) end start_new_session_for(user) redirect_to root_path, notice: "Successfully signed in via OIDC" rescue OpenIDConnect::Exception, Rack::OAuth2::Client::Error => e Rails.logger.error "OIDC authentication failed: #{e.message}" redirect_to new_session_path, alert: "Authentication failed: #{e.message}" end private def oidc_client @oidc_client ||= begin discovery = OpenIDConnect::Discovery::Provider::Config.discover!(ENV['OIDC_DISCOVERY_URL']) OpenIDConnect::Client.new( identifier: ENV['OIDC_CLIENT_ID'], secret: ENV['OIDC_CLIENT_SECRET'], redirect_uri: ENV['OIDC_REDIRECT_URI'] || oidc_callback_url, authorization_endpoint: discovery.authorization_endpoint, token_endpoint: discovery.token_endpoint, userinfo_endpoint: discovery.userinfo_endpoint ) end end def generate_state_token token = SecureRandom.hex(32) session[:oidc_state] = token token end def generate_nonce SecureRandom.hex(32) end def valid_state_token?(state) state.present? && session[:oidc_state] == state end def oidc_callback_url "#{request.base_url}/auth/oidc/callback" end end