+ /**
+ * Does this request appear to be a web-service request?
+ *
+ * It is important to distinguish regular browser-page-loads from web-service-requests. Regular
+ * page-loads can be CSRF vectors, and we don't web-services to run via CSRF.
+ *
+ * @return bool
+ * TRUE if the current request appears to either XMLHttpRequest or non-browser-based.
+ * Indicated by either (a) custom headers like `X-Request-With`/`X-Civi-Auth`
+ * or (b) strong-secret-params that could theoretically appear in URL bar but which
+ * cannot be meaningfully forged for CSRF purposes (like `?api_key=SECRET` or `?_authx=SECRET`).
+ * FALSE if the current request looks like a standard browser request. This request may be generated by
+ * <A HREF>, <IFRAME>, <IMG>, `Location:`, or similar CSRF vector.
+ */
+ public static function isWebServiceRequest(): bool {
+ if (($_SERVER['HTTP_X_REQUESTED_WITH'] ?? NULL) === 'XMLHttpRequest') {
+ return TRUE;
+ }
+
+ // If authx is enabled, and if the user gives a credential, it will store metadata.
+ $authx = \CRM_Core_Session::singleton()->get('authx');
+ $allowFlows = [
+ // Some flows are resistant to CSRF. Allow these:
+
+ // <legacyrest> Current request has valid `?api_key=SECRET&key=SECRET` ==> Strong-secret params
+ 'legacyrest',
+
+ // <param> Current request has valid `?_authx=SECRET` ==> Strong-secret param
+ 'param',
+
+ // <xheader> Current request has valid `X-Civi-Auth:` ==> Custom header AND strong-secret param
+ 'xheader',
+
+ // Other flows are not resistant to CSRF on their own (need combo w/`X-Requested-With:`).
+ // Ignore these:
+ // <login> Relies on a session `Cookie:` (which browsers re-send automatically).
+ // <auto> First request might be resistant, but all others use session `Cookie:`.
+ // <header> Browsers often retain list of credentials and re-send automatically.
+ ];
+
+ if (!empty($authx) && in_array($authx['flow'], $allowFlows)) {
+ return TRUE;
+ }
+
+ return FALSE;
+ }
+