More OpenID Conformance test fixes - work with POST, correct auth code character set, correct no-store cache headers
This commit is contained in:
@@ -52,12 +52,24 @@ module Authentication
|
|||||||
# Extract root domain for cross-subdomain cookies (required for forward auth)
|
# Extract root domain for cross-subdomain cookies (required for forward auth)
|
||||||
domain = extract_root_domain(request.host)
|
domain = extract_root_domain(request.host)
|
||||||
|
|
||||||
cookie_options = {
|
# Set cookie options based on environment
|
||||||
|
# Production: Use SameSite=None to allow cross-site cookies (needed for OIDC conformance testing)
|
||||||
|
# Development: Use SameSite=Lax since HTTPS might not be available
|
||||||
|
cookie_options = if Rails.env.production?
|
||||||
|
{
|
||||||
|
value: session.id,
|
||||||
|
httponly: true,
|
||||||
|
same_site: :none, # Allow cross-site cookies for OIDC testing
|
||||||
|
secure: true # Required for SameSite=None
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
value: session.id,
|
value: session.id,
|
||||||
httponly: true,
|
httponly: true,
|
||||||
same_site: :lax,
|
same_site: :lax,
|
||||||
secure: Rails.env.production?
|
secure: false
|
||||||
}
|
}
|
||||||
|
end
|
||||||
|
|
||||||
# Set domain for cross-subdomain authentication if we can extract it
|
# Set domain for cross-subdomain authentication if we can extract it
|
||||||
cookie_options[:domain] = domain if domain.present?
|
cookie_options[:domain] = domain if domain.present?
|
||||||
|
|||||||
@@ -210,6 +210,9 @@ class OidcController < ApplicationController
|
|||||||
# This creates a fresh authentication event with a new auth_time
|
# This creates a fresh authentication event with a new auth_time
|
||||||
Current.session&.destroy!
|
Current.session&.destroy!
|
||||||
|
|
||||||
|
# Clear the session cookie so the user is truly logged out
|
||||||
|
cookies.delete(:session_id)
|
||||||
|
|
||||||
# Store the current URL (which contains all OAuth params) for redirect after login
|
# Store the current URL (which contains all OAuth params) for redirect after login
|
||||||
# Remove prompt=login to prevent infinite re-auth loop
|
# Remove prompt=login to prevent infinite re-auth loop
|
||||||
return_url = request.url.sub(/&prompt=login(?=&|$)|\?prompt=login&?/, '\1')
|
return_url = request.url.sub(/&prompt=login(?=&|$)|\?prompt=login&?/, '\1')
|
||||||
@@ -536,6 +539,10 @@ class OidcController < ApplicationController
|
|||||||
scopes: auth_code.scope
|
scopes: auth_code.scope
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# RFC6749-5.1: Token endpoint MUST return Cache-Control: no-store
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
|
||||||
# Return tokens
|
# Return tokens
|
||||||
render json: {
|
render json: {
|
||||||
access_token: access_token_record.plaintext_token, # Opaque token
|
access_token: access_token_record.plaintext_token, # Opaque token
|
||||||
@@ -665,6 +672,10 @@ class OidcController < ApplicationController
|
|||||||
scopes: refresh_token_record.scope
|
scopes: refresh_token_record.scope
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# RFC6749-5.1: Token endpoint MUST return Cache-Control: no-store
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
|
||||||
# Return new tokens
|
# Return new tokens
|
||||||
render json: {
|
render json: {
|
||||||
access_token: new_access_token.plaintext_token, # Opaque token
|
access_token: new_access_token.plaintext_token, # Opaque token
|
||||||
@@ -763,6 +774,10 @@ class OidcController < ApplicationController
|
|||||||
application = access_token.application
|
application = access_token.application
|
||||||
claims.merge!(application.custom_claims_for_user(user))
|
claims.merge!(application.custom_claims_for_user(user))
|
||||||
|
|
||||||
|
# Security: Don't cache user data responses
|
||||||
|
response.headers["Cache-Control"] = "no-store"
|
||||||
|
response.headers["Pragma"] = "no-cache"
|
||||||
|
|
||||||
render json: claims
|
render json: claims
|
||||||
end
|
end
|
||||||
|
|
||||||
@@ -907,8 +922,8 @@ class OidcController < ApplicationController
|
|||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
# Validate code verifier format (base64url-encoded, 43-128 characters)
|
# Validate code verifier format (per RFC 7636: [A-Za-z0-9\-._~], 43-128 characters)
|
||||||
unless code_verifier.match?(/\A[A-Za-z0-9\-_]{43,128}\z/)
|
unless code_verifier.match?(/\A[A-Za-z0-9\.\-_~]{43,128}\z/)
|
||||||
return {
|
return {
|
||||||
valid: false,
|
valid: false,
|
||||||
error: "invalid_request",
|
error: "invalid_request",
|
||||||
|
|||||||
@@ -87,7 +87,10 @@ class SessionsController < ApplicationController
|
|||||||
|
|
||||||
# Sign in successful (password only)
|
# Sign in successful (password only)
|
||||||
start_new_session_for user, acr: "1"
|
start_new_session_for user, acr: "1"
|
||||||
redirect_to after_authentication_url, notice: "Signed in successfully.", allow_other_host: true
|
|
||||||
|
# Use status: :see_other to ensure browser makes a GET request
|
||||||
|
# This prevents Turbo from converting it to a TURBO_STREAM request
|
||||||
|
redirect_to after_authentication_url, notice: "Signed in successfully.", allow_other_host: true, status: :see_other
|
||||||
end
|
end
|
||||||
|
|
||||||
def verify_totp
|
def verify_totp
|
||||||
|
|||||||
Reference in New Issue
Block a user