Add backchannel logout, per application logout.
This commit is contained in:
@@ -16,16 +16,82 @@ class ActiveSessionsController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
# Send backchannel logout notification before revoking consent
|
||||
if application.supports_backchannel_logout?
|
||||
BackchannelLogoutJob.perform_later(
|
||||
user_id: @user.id,
|
||||
application_id: application.id,
|
||||
consent_sid: consent.sid
|
||||
)
|
||||
Rails.logger.info "ActiveSessionsController: Enqueued backchannel logout for #{application.name}"
|
||||
end
|
||||
|
||||
# Revoke all tokens for this user-application pair
|
||||
now = Time.current
|
||||
revoked_access_tokens = OidcAccessToken.where(application: application, user: @user, revoked_at: nil)
|
||||
.update_all(revoked_at: now)
|
||||
revoked_refresh_tokens = OidcRefreshToken.where(application: application, user: @user, revoked_at: nil)
|
||||
.update_all(revoked_at: now)
|
||||
|
||||
Rails.logger.info "ActiveSessionsController: Revoked #{revoked_access_tokens} access tokens and #{revoked_refresh_tokens} refresh tokens for #{application.name}"
|
||||
|
||||
# Revoke the consent
|
||||
consent.destroy
|
||||
redirect_to active_sessions_path, notice: "Successfully revoked access to #{application.name}."
|
||||
end
|
||||
|
||||
def logout_from_app
|
||||
@user = Current.session.user
|
||||
application = Application.find(params[:application_id])
|
||||
|
||||
# Check if user has consent for this application
|
||||
consent = @user.oidc_user_consents.find_by(application: application)
|
||||
unless consent
|
||||
redirect_to root_path, alert: "No active session found for this application."
|
||||
return
|
||||
end
|
||||
|
||||
# Send backchannel logout notification
|
||||
if application.supports_backchannel_logout?
|
||||
BackchannelLogoutJob.perform_later(
|
||||
user_id: @user.id,
|
||||
application_id: application.id,
|
||||
consent_sid: consent.sid
|
||||
)
|
||||
Rails.logger.info "ActiveSessionsController: Enqueued backchannel logout for #{application.name}"
|
||||
end
|
||||
|
||||
# Revoke all tokens for this user-application pair
|
||||
now = Time.current
|
||||
revoked_access_tokens = OidcAccessToken.where(application: application, user: @user, revoked_at: nil)
|
||||
.update_all(revoked_at: now)
|
||||
revoked_refresh_tokens = OidcRefreshToken.where(application: application, user: @user, revoked_at: nil)
|
||||
.update_all(revoked_at: now)
|
||||
|
||||
Rails.logger.info "ActiveSessionsController: Logged out from #{application.name} - revoked #{revoked_access_tokens} access tokens and #{revoked_refresh_tokens} refresh tokens"
|
||||
|
||||
# Keep the consent intact - this is the key difference from revoke_consent
|
||||
redirect_to root_path, notice: "Successfully logged out of #{application.name}."
|
||||
end
|
||||
|
||||
def revoke_all_consents
|
||||
@user = Current.session.user
|
||||
count = @user.oidc_user_consents.count
|
||||
consents = @user.oidc_user_consents.includes(:application)
|
||||
count = consents.count
|
||||
|
||||
if count > 0
|
||||
# Send backchannel logout notifications before revoking consents
|
||||
consents.each do |consent|
|
||||
next unless consent.application.supports_backchannel_logout?
|
||||
|
||||
BackchannelLogoutJob.perform_later(
|
||||
user_id: @user.id,
|
||||
application_id: consent.application.id,
|
||||
consent_sid: consent.sid
|
||||
)
|
||||
end
|
||||
Rails.logger.info "ActiveSessionsController: Enqueued #{count} backchannel logout notifications"
|
||||
|
||||
@user.oidc_user_consents.destroy_all
|
||||
redirect_to active_sessions_path, notice: "Successfully revoked access to #{count} applications."
|
||||
else
|
||||
|
||||
@@ -100,6 +100,7 @@ module Admin
|
||||
params.require(:application).permit(
|
||||
:name, :slug, :app_type, :active, :redirect_uris, :description, :metadata,
|
||||
:domain_pattern, :landing_url, :access_token_ttl, :refresh_token_ttl, :id_token_ttl,
|
||||
:icon, :backchannel_logout_uri,
|
||||
headers_config: {}
|
||||
).tap do |whitelisted|
|
||||
# Remove client_secret from params if present (shouldn't be updated via form)
|
||||
|
||||
@@ -23,7 +23,9 @@ class OidcController < ApplicationController
|
||||
scopes_supported: ["openid", "profile", "email", "groups"],
|
||||
token_endpoint_auth_methods_supported: ["client_secret_post", "client_secret_basic"],
|
||||
claims_supported: ["sub", "email", "email_verified", "name", "preferred_username", "groups", "admin"],
|
||||
code_challenge_methods_supported: ["plain", "S256"]
|
||||
code_challenge_methods_supported: ["plain", "S256"],
|
||||
backchannel_logout_supported: true,
|
||||
backchannel_logout_session_supported: true
|
||||
}
|
||||
|
||||
render json: config
|
||||
@@ -627,6 +629,11 @@ class OidcController < ApplicationController
|
||||
|
||||
# If user is authenticated, log them out
|
||||
if authenticated?
|
||||
user = Current.session.user
|
||||
|
||||
# Send backchannel logout notifications to all connected applications
|
||||
send_backchannel_logout_notifications(user)
|
||||
|
||||
# Invalidate the current session
|
||||
Current.session&.destroy
|
||||
reset_session
|
||||
@@ -766,4 +773,26 @@ class OidcController < ApplicationController
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
def send_backchannel_logout_notifications(user)
|
||||
# Find all active OIDC consents for this user
|
||||
consents = OidcUserConsent.where(user: user).includes(:application)
|
||||
|
||||
consents.each do |consent|
|
||||
# Skip if application doesn't support backchannel logout
|
||||
next unless consent.application.supports_backchannel_logout?
|
||||
|
||||
# Enqueue background job to send logout notification
|
||||
BackchannelLogoutJob.perform_later(
|
||||
user_id: user.id,
|
||||
application_id: consent.application.id,
|
||||
consent_sid: consent.sid
|
||||
)
|
||||
end
|
||||
|
||||
Rails.logger.info "OidcController: Enqueued #{consents.count} backchannel logout notifications for user #{user.id}"
|
||||
rescue => e
|
||||
# Log error but don't block logout
|
||||
Rails.logger.error "OidcController: Failed to enqueue backchannel logout: #{e.class} - #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -134,6 +134,12 @@ class SessionsController < ApplicationController
|
||||
end
|
||||
|
||||
def destroy
|
||||
# Send backchannel logout notifications before terminating session
|
||||
if authenticated?
|
||||
user = Current.session.user
|
||||
send_backchannel_logout_notifications(user)
|
||||
end
|
||||
|
||||
terminate_session
|
||||
redirect_to signin_path, status: :see_other, notice: "Signed out successfully."
|
||||
end
|
||||
@@ -311,4 +317,26 @@ class SessionsController < ApplicationController
|
||||
nil
|
||||
end
|
||||
end
|
||||
|
||||
def send_backchannel_logout_notifications(user)
|
||||
# Find all active OIDC consents for this user
|
||||
consents = OidcUserConsent.where(user: user).includes(:application)
|
||||
|
||||
consents.each do |consent|
|
||||
# Skip if application doesn't support backchannel logout
|
||||
next unless consent.application.supports_backchannel_logout?
|
||||
|
||||
# Enqueue background job to send logout notification
|
||||
BackchannelLogoutJob.perform_later(
|
||||
user_id: user.id,
|
||||
application_id: consent.application.id,
|
||||
consent_sid: consent.sid
|
||||
)
|
||||
end
|
||||
|
||||
Rails.logger.info "SessionsController: Enqueued #{consents.count} backchannel logout notifications for user #{user.id}"
|
||||
rescue => e
|
||||
# Log error but don't block logout
|
||||
Rails.logger.error "SessionsController: Failed to enqueue backchannel logout: #{e.class} - #{e.message}"
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user