More OpenID Conformance test fixes - work with POST, correct auth code character set, correct no-store cache headers

This commit is contained in:
Dan Milne
2026-01-03 12:28:43 +11:00
parent 16e34ffaf0
commit f4a697ae9b
3 changed files with 39 additions and 9 deletions

View File

@@ -52,12 +52,24 @@ module Authentication
# Extract root domain for cross-subdomain cookies (required for forward auth)
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,
httponly: true,
same_site: :lax,
secure: Rails.env.production?
secure: false
}
end
# Set domain for cross-subdomain authentication if we can extract it
cookie_options[:domain] = domain if domain.present?

View File

@@ -210,6 +210,9 @@ class OidcController < ApplicationController
# This creates a fresh authentication event with a new auth_time
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
# Remove prompt=login to prevent infinite re-auth loop
return_url = request.url.sub(/&prompt=login(?=&|$)|\?prompt=login&?/, '\1')
@@ -536,6 +539,10 @@ class OidcController < ApplicationController
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
render json: {
access_token: access_token_record.plaintext_token, # Opaque token
@@ -665,6 +672,10 @@ class OidcController < ApplicationController
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
render json: {
access_token: new_access_token.plaintext_token, # Opaque token
@@ -763,6 +774,10 @@ class OidcController < ApplicationController
application = access_token.application
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
end
@@ -907,8 +922,8 @@ class OidcController < ApplicationController
}
end
# Validate code verifier format (base64url-encoded, 43-128 characters)
unless code_verifier.match?(/\A[A-Za-z0-9\-_]{43,128}\z/)
# 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/)
return {
valid: false,
error: "invalid_request",

View File

@@ -87,7 +87,10 @@ class SessionsController < ApplicationController
# Sign in successful (password only)
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
def verify_totp