PKCE is now default enabled. You can now create public / no-secret apps OIDC apps
This commit is contained in:
@@ -26,16 +26,17 @@ module Admin
|
||||
@application.allowed_groups = Group.where(id: group_ids)
|
||||
end
|
||||
|
||||
# Get the plain text client secret to show one time
|
||||
# Get the plain text client secret to show one time (confidential clients only)
|
||||
client_secret = nil
|
||||
if @application.oidc?
|
||||
if @application.oidc? && @application.confidential_client?
|
||||
client_secret = @application.generate_new_client_secret!
|
||||
end
|
||||
|
||||
if @application.oidc? && client_secret
|
||||
if @application.oidc?
|
||||
flash[:notice] = "Application created successfully."
|
||||
flash[:client_id] = @application.client_id
|
||||
flash[:client_secret] = client_secret
|
||||
flash[:client_secret] = client_secret if client_secret
|
||||
flash[:public_client] = true if @application.public_client?
|
||||
else
|
||||
flash[:notice] = "Application created successfully."
|
||||
end
|
||||
@@ -74,15 +75,20 @@ module Admin
|
||||
|
||||
def regenerate_credentials
|
||||
if @application.oidc?
|
||||
# Generate new client ID and secret
|
||||
# Generate new client ID (always)
|
||||
new_client_id = SecureRandom.urlsafe_base64(32)
|
||||
client_secret = @application.generate_new_client_secret!
|
||||
|
||||
@application.update!(client_id: new_client_id)
|
||||
|
||||
flash[:notice] = "Credentials regenerated successfully."
|
||||
flash[:client_id] = @application.client_id
|
||||
flash[:client_secret] = client_secret
|
||||
|
||||
# Generate new client secret only for confidential clients
|
||||
if @application.confidential_client?
|
||||
client_secret = @application.generate_new_client_secret!
|
||||
flash[:client_secret] = client_secret
|
||||
else
|
||||
flash[:public_client] = true
|
||||
end
|
||||
|
||||
redirect_to admin_application_path(@application)
|
||||
else
|
||||
@@ -97,15 +103,24 @@ module Admin
|
||||
end
|
||||
|
||||
def application_params
|
||||
params.require(:application).permit(
|
||||
permitted = 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)
|
||||
whitelisted.delete(:client_secret)
|
||||
:icon, :backchannel_logout_uri, :is_public_client, :require_pkce
|
||||
)
|
||||
|
||||
# Handle headers_config - it comes as a JSON string from the text area
|
||||
if params[:application][:headers_config].present?
|
||||
begin
|
||||
permitted[:headers_config] = JSON.parse(params[:application][:headers_config])
|
||||
rescue JSON::ParserError
|
||||
permitted[:headers_config] = {}
|
||||
end
|
||||
end
|
||||
|
||||
# Remove client_secret from params if present (shouldn't be updated via form)
|
||||
permitted.delete(:client_secret)
|
||||
permitted
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -296,22 +296,33 @@ class OidcController < ApplicationController
|
||||
end
|
||||
|
||||
def handle_authorization_code_grant
|
||||
|
||||
# Get client credentials from Authorization header or params
|
||||
client_id, client_secret = extract_client_credentials
|
||||
|
||||
unless client_id && client_secret
|
||||
render json: { error: "invalid_client" }, status: :unauthorized
|
||||
unless client_id
|
||||
render json: { error: "invalid_client", error_description: "client_id is required" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
|
||||
# Find and validate the application
|
||||
# Find the application
|
||||
application = Application.find_by(client_id: client_id)
|
||||
unless application && application.authenticate_client_secret(client_secret)
|
||||
render json: { error: "invalid_client" }, status: :unauthorized
|
||||
unless application
|
||||
render json: { error: "invalid_client", error_description: "Unknown client" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
|
||||
# Validate client credentials based on client type
|
||||
if application.public_client?
|
||||
# Public clients don't have a secret - they MUST use PKCE (checked later)
|
||||
Rails.logger.info "OAuth: Public client authentication for #{application.name}"
|
||||
else
|
||||
# Confidential clients MUST provide valid client_secret
|
||||
unless client_secret.present? && application.authenticate_client_secret(client_secret)
|
||||
render json: { error: "invalid_client", error_description: "Invalid client credentials" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Check if application is active
|
||||
unless application.active?
|
||||
Rails.logger.error "OAuth: Token request for inactive application: #{application.name}"
|
||||
@@ -371,8 +382,8 @@ class OidcController < ApplicationController
|
||||
return
|
||||
end
|
||||
|
||||
# Validate PKCE if code challenge is present
|
||||
pkce_result = validate_pkce(auth_code, code_verifier)
|
||||
# Validate PKCE - required for public clients and optionally for confidential clients
|
||||
pkce_result = validate_pkce(application, auth_code, code_verifier)
|
||||
unless pkce_result[:valid]
|
||||
render json: {
|
||||
error: pkce_result[:error],
|
||||
@@ -433,18 +444,30 @@ class OidcController < ApplicationController
|
||||
# Get client credentials from Authorization header or params
|
||||
client_id, client_secret = extract_client_credentials
|
||||
|
||||
unless client_id && client_secret
|
||||
render json: { error: "invalid_client" }, status: :unauthorized
|
||||
unless client_id
|
||||
render json: { error: "invalid_client", error_description: "client_id is required" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
|
||||
# Find and validate the application
|
||||
# Find the application
|
||||
application = Application.find_by(client_id: client_id)
|
||||
unless application && application.authenticate_client_secret(client_secret)
|
||||
render json: { error: "invalid_client" }, status: :unauthorized
|
||||
unless application
|
||||
render json: { error: "invalid_client", error_description: "Unknown client" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
|
||||
# Validate client credentials based on client type
|
||||
if application.public_client?
|
||||
# Public clients don't have a secret
|
||||
Rails.logger.info "OAuth: Public client refresh token request for #{application.name}"
|
||||
else
|
||||
# Confidential clients MUST provide valid client_secret
|
||||
unless client_secret.present? && application.authenticate_client_secret(client_secret)
|
||||
render json: { error: "invalid_client", error_description: "Invalid client credentials" }, status: :unauthorized
|
||||
return
|
||||
end
|
||||
end
|
||||
|
||||
# Check if application is active
|
||||
unless application.active?
|
||||
Rails.logger.error "OAuth: Refresh token request for inactive application: #{application.name}"
|
||||
@@ -716,11 +739,26 @@ class OidcController < ApplicationController
|
||||
|
||||
private
|
||||
|
||||
def validate_pkce(auth_code, code_verifier)
|
||||
# Skip PKCE validation if no code challenge was stored (legacy clients)
|
||||
return { valid: true } unless auth_code.code_challenge.present?
|
||||
def validate_pkce(application, auth_code, code_verifier)
|
||||
# Check if PKCE is required for this application
|
||||
pkce_required = application.requires_pkce?
|
||||
pkce_provided = auth_code.code_challenge.present?
|
||||
|
||||
# PKCE is required but no verifier provided
|
||||
# If PKCE is required but wasn't provided during authorization
|
||||
if pkce_required && !pkce_provided
|
||||
client_type = application.public_client? ? "public clients" : "this application"
|
||||
return {
|
||||
valid: false,
|
||||
error: "invalid_request",
|
||||
error_description: "PKCE is required for #{client_type}. code_challenge must be provided during authorization.",
|
||||
status: :bad_request
|
||||
}
|
||||
end
|
||||
|
||||
# Skip validation if no code challenge was stored (legacy clients without PKCE requirement)
|
||||
return { valid: true } unless pkce_provided
|
||||
|
||||
# PKCE was provided during authorization but no verifier sent with token request
|
||||
unless code_verifier.present?
|
||||
return {
|
||||
valid: false,
|
||||
|
||||
Reference in New Issue
Block a user