lib/rodauth/features/oidc.rb in rodauth-oauth-0.4.3 vs lib/rodauth/features/oidc.rb in rodauth-oauth-0.5.0

- old
+ new

@@ -12,10 +12,11 @@ }.freeze VALID_METADATA_KEYS = %i[ issuer authorization_endpoint + end_session_endpoint token_endpoint userinfo_endpoint jwks_uri registration_endpoint scopes_supported @@ -73,10 +74,14 @@ auth_value_method :oauth_prompt_login_cookie_key, "_rodauth_oauth_prompt_login" auth_value_method :oauth_prompt_login_cookie_options, {}.freeze auth_value_method :oauth_prompt_login_interval, 5 * 60 * 60 # 5 minutes + # logout + auth_value_method :oauth_applications_post_logout_redirect_uri_column, :post_logout_redirect_uri + auth_value_method :use_rp_initiated_logout?, false + auth_value_methods(:get_oidc_param, :get_additional_param) # /userinfo route(:userinfo) do |r| next unless is_authorization_server? @@ -106,14 +111,85 @@ throw_json_response_error(authorization_required_error_status, "invalid_token") end end - def openid_configuration(issuer = nil) + # /oidc-logout + route(:oidc_logout) do |r| + next unless use_rp_initiated_logout? + + before_oidc_logout_route + require_authorizable_account + + # OpenID Providers MUST support the use of the HTTP GET and POST methods + r.on method: %i[get post] do + catch_error do + validate_oidc_logout_params + + # + # why this is done: + # + # we need to decode the id token in order to get the application, because, if the + # signing key is application-specific, we don't know how to verify the signature + # beforehand. Hence, we have to do it twice: decode-and-do-not-verify, initialize + # the @oauth_application, and then decode-and-verify. + # + oauth_token = jwt_decode(param("id_token_hint"), verify_claims: false) + oauth_application_id = oauth_token["client_id"] + + # check whether ID token belongs to currently logged-in user + redirect_response_error("invalid_request") unless oauth_token["sub"] == jwt_subject( + oauth_tokens_account_id_column => account_id, + oauth_tokens_oauth_application_id_column => oauth_application_id + ) + + # When an id_token_hint parameter is present, the OP MUST validate that it was the issuer of the ID Token. + redirect_response_error("invalid_request") unless oauth_token && oauth_token["iss"] == issuer + + # now let's logout from IdP + transaction do + before_logout + logout + after_logout + end + + if (post_logout_redirect_uri = param_or_nil("post_logout_redirect_uri")) + catch(:default_logout_redirect) do + oauth_application = db[oauth_applications_table].where(oauth_applications_client_id_column => oauth_token["client_id"]).first + + throw(:default_logout_redirect) unless oauth_application + + post_logout_redirect_uris = oauth_application[oauth_applications_post_logout_redirect_uri_column].split(" ") + + throw(:default_logout_redirect) unless post_logout_redirect_uris.include?(post_logout_redirect_uri) + + if (state = param_or_nil("state")) + post_logout_redirect_uri = URI(post_logout_redirect_uri) + params = ["state=#{state}"] + params << post_logout_redirect_uri.query if post_logout_redirect_uri.query + post_logout_redirect_uri.query = params.join("&") + post_logout_redirect_uri = post_logout_redirect_uri.to_s + end + + redirect(post_logout_redirect_uri) + end + + end + + # regular logout procedure + set_notice_flash(logout_notice_flash) + redirect(logout_redirect) + end + + redirect_response_error("invalid_request") + end + end + + def openid_configuration(alt_issuer = nil) request.on(".well-known/openid-configuration") do request.get do - json_response_success(openid_configuration_body(issuer), cache: true) + json_response_success(openid_configuration_body(alt_issuer), cache: true) end end end def webfinger @@ -340,10 +416,22 @@ params = json_access_token_payload(oauth_token) params.delete("access_token") params end + # Logout + + def validate_oidc_logout_params + redirect_response_error("invalid_request") unless param_or_nil("id_token_hint") + # check if valid token hint type + return unless (redirect_uri = param_or_nil("post_logout_redirect_uri")) + + return if check_valid_uri?(redirect_uri) + + redirect_response_error("invalid_request") + end + # Metadata def openid_configuration_body(path) metadata = oauth_server_metadata_body(path).select do |k, _| VALID_METADATA_KEYS.include?(k) @@ -366,9 +454,10 @@ response_types_supported += ["none", "id_token", "code token", "code id_token", "id_token token", "code id_token token"] end metadata.merge( userinfo_endpoint: userinfo_url, + end_session_endpoint: (oidc_logout_url if use_rp_initiated_logout?), response_types_supported: response_types_supported, subject_types_supported: [oauth_jwt_subject_type], id_token_signing_alg_values_supported: metadata[:token_endpoint_auth_signing_alg_values_supported], id_token_encryption_alg_values_supported: [oauth_jwt_jwe_algorithm].compact,