OpenID Conformance: user info endpoint should support get and post requets, not just get
Some checks failed
CI / scan_ruby (push) Has been cancelled
CI / scan_js (push) Has been cancelled
CI / scan_container (push) Has been cancelled
CI / lint (push) Has been cancelled
CI / test (push) Has been cancelled
CI / system-test (push) Has been cancelled

This commit is contained in:
Dan Milne
2026-01-02 15:26:39 +11:00
parent b09ddf6db5
commit f67a73821c
4 changed files with 30 additions and 11 deletions

View File

@@ -600,7 +600,8 @@ class OidcController < ApplicationController
render json: {error: "invalid_grant"}, status: :bad_request render json: {error: "invalid_grant"}, status: :bad_request
end end
# GET /oauth/userinfo # GET/POST /oauth/userinfo
# OIDC Core spec: UserInfo endpoint MUST support GET, SHOULD support POST
def userinfo def userinfo
# Extract access token from Authorization header # Extract access token from Authorization header
auth_header = request.headers["Authorization"] auth_header = request.headers["Authorization"]
@@ -636,17 +637,29 @@ class OidcController < ApplicationController
consent = OidcUserConsent.find_by(user: user, application: access_token.application) consent = OidcUserConsent.find_by(user: user, application: access_token.application)
subject = consent&.sid || user.id.to_s subject = consent&.sid || user.id.to_s
# Return user claims # Parse scopes from access token (space-separated string)
requested_scopes = access_token.scope.to_s.split
# Return user claims (filter by scope per OIDC Core spec)
# Required claims (always included)
claims = { claims = {
sub: subject, sub: subject
email: user.email_address,
email_verified: true,
preferred_username: user.email_address,
name: user.name.presence || user.email_address
} }
# Add groups if user has any # Email claims (only if 'email' scope requested)
if user.groups.any? if requested_scopes.include?("email")
claims[:email] = user.email_address
claims[:email_verified] = true
end
# Profile claims (only if 'profile' scope requested)
if requested_scopes.include?("profile")
claims[:preferred_username] = user.email_address
claims[:name] = user.name.presence || user.email_address
end
# Groups claim (only if 'groups' scope requested)
if requested_scopes.include?("groups") && user.groups.any?
claims[:groups] = user.groups.pluck(:name) claims[:groups] = user.groups.pluck(:name)
end end

View File

@@ -30,7 +30,7 @@ Rails.application.routes.draw do
post "/oauth/authorize/consent", to: "oidc#consent", as: :oauth_consent post "/oauth/authorize/consent", to: "oidc#consent", as: :oauth_consent
post "/oauth/token", to: "oidc#token" post "/oauth/token", to: "oidc#token"
post "/oauth/revoke", to: "oidc#revoke" post "/oauth/revoke", to: "oidc#revoke"
get "/oauth/userinfo", to: "oidc#userinfo" match "/oauth/userinfo", to: "oidc#userinfo", via: [:get, :post]
get "/logout", to: "oidc#logout" get "/logout", to: "oidc#logout"
# ForwardAuth / Trusted Header SSO # ForwardAuth / Trusted Header SSO

View File

@@ -228,7 +228,11 @@ class OidcRefreshTokenControllerTest < ActionDispatch::IntegrationTest
assert_response :success assert_response :success
json = JSON.parse(response.body) json = JSON.parse(response.body)
assert_equal @user.id.to_s, json["sub"]
# Should return pairwise SID from consent (alice has consent for kavita_app in fixtures)
consent = OidcUserConsent.find_by(user: @user, application: @application)
expected_sub = consent&.sid || @user.id.to_s
assert_equal expected_sub, json["sub"]
assert_equal @user.email_address, json["email"] assert_equal @user.email_address, json["email"]
end end
end end

View File

@@ -5,9 +5,11 @@ alice_consent:
application: kavita_app application: kavita_app
scopes_granted: openid profile email scopes_granted: openid profile email
granted_at: 2025-10-24 16:57:39 granted_at: 2025-10-24 16:57:39
sid: alice-kavita-sid-12345
bob_consent: bob_consent:
user: bob user: bob
application: another_app application: another_app
scopes_granted: openid email groups scopes_granted: openid email groups
granted_at: 2025-10-24 16:57:39 granted_at: 2025-10-24 16:57:39
sid: bob-another-sid-67890