kgriffs: I think this makes sense. It is a breaking change, but one that should be easy for developers to accommodate. A couple testing suggestions inline.
diff --git a/falcon/api.py b/falcon/api.py
index cdc2c66..1a6b944 100644
--- a/falcon/api.py
+++ b/falcon/api.py
@@ -178,7 +178,8 @@ class API(object):
                 # e.g. a 404.
                 responder, params, resource = self._get_responder(req)
 
-                self._call_rsrc_mw(middleware_stack, req, resp, resource)
+                self._call_rsrc_mw(middleware_stack, req, resp, resource,
+                                   params)
 
                 responder(req, resp, **params)
                 self._call_resp_mw(middleware_stack, req, resp, resource)
@@ -537,13 +538,13 @@ class API(object):
             # Put executed component on the stack
             stack.append(component)  # keep track from outside
 
-    def _call_rsrc_mw(self, stack, req, resp, resource):
+    def _call_rsrc_mw(self, stack, req, resp, resource, params):
         """Run process_resource middleware methods."""
 
         for component in self._middleware:
             _, process_resource, _ = component
             if process_resource is not None:
-                process_resource(req, resp, resource)
+                process_resource(req, resp, resource, params)
 
     def _call_resp_mw(self, stack, req, resp, resource):
         """Run process_response middleware."""

Pass params to process_resource
This is needed for feature parity with global *before* hooks. We can use a shim to avoid breaking existing middleware.
diff --git a/tests/test_httpstatus.py b/tests/test_httpstatus.py
index 3569e6b..f525be1 100644
--- a/tests/test_httpstatus.py
+++ b/tests/test_httpstatus.py
@@ -166,7 +166,7 @@ class TestHTTPStatusWithGlobalHooks(testing.TestBase):
     def test_raise_status_in_process_resource(self):
         """ Make sure we can raise status from middleware process resource """
         class TestMiddleware:
-            def process_resource(self, req, resp, resource):
+            def process_resource(self, req, resp, resource, params):
                 raise HTTPStatus(falcon.HTTP_200,
                                  headers={"X-Failed": "False"},
                                  body="Pass")
diff --git a/tests/test_middlewares.py b/tests/test_middlewares.py
index 226e57f..519852a 100644
--- a/tests/test_middlewares.py
+++ b/tests/test_middlewares.py
@@ -1,3 +1,5 @@
+import json
+
 import falcon
 import falcon.testing as testing
 from datetime import datetime
@@ -11,7 +13,7 @@ class RequestTimeMiddleware(object):
         global context
         context['start_time'] = datetime.utcnow()
 
-    def process_resource(self, req, resp, resource):
+    def process_resource(self, req, resp, resource, params):
         global context
         context['mid_time'] = datetime.utcnow()
 
@@ -34,7 +36,7 @@ class ExecutedFirstMiddleware(object):
         context['executed_methods'].append(
             '{0}.{1}'.format(self.__class__.__name__, 'process_request'))
 
-    def process_resource(self, req, resp, resource):
+    def process_resource(self, req, resp, resource, params):
         global context
         context['executed_methods'].append(
             '{0}.{1}'.format(self.__class__.__name__, 'process_resource'))
@@ -55,9 +57,17 @@ class RemoveBasePathMiddleware(object):
         req.path = req.path.replace('/base_path', '', 1)
 
 
+class AccessParamsMiddleware(object):
+
+    def process_resource(self, req, resp, resource, params):
+        global context
+        params['added'] = True
+        context['params'] = params
+
+
 class MiddlewareClassResource(object):
 
-    def on_get(self, req, resp):
+    def on_get(self, req, resp, **kwargs):
         resp.status = falcon.HTTP_200
         resp.body = {'status': 'ok'}
 
@@ -368,3 +378,23 @@ class TestRemoveBasePathMiddleware(TestMiddleware):
         self.assertEqual(self.srmock.status, falcon.HTTP_200)
         self.simulate_request('/base_pathIncorrect/sub_path')
         self.assertEqual(self.srmock.status, falcon.HTTP_404)
+
+
+class TestResourceMiddleware(TestMiddleware):
+
+    def test_can_access_resource_params(self):
+        """Test that params can be accessed from within process_resource"""
+        global context
+
+        class Resource:
+            def on_get(self, req, resp, **params):
+                resp.data = json.dumps(params)
+
+        self.api = falcon.API(middleware=AccessParamsMiddleware())
+        self.api.add_route('/path/{id}', Resource())
+        resp = self.simulate_request('/path/22')
+
+        self.assertIn('params', context)
+        self.assertTrue(context['params'])
+        self.assertEqual(context['params']['id'], '22')
+        self.assertEqual(json.loads(resp[0]), {"added": True, "id": "22"})

pip install -e .
parse_log_pytest
pytest
nose coverage ddt pyyaml requests testtools pytest
apt-get update apt-get install -y gcc
3.7
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs @ file:///croot/attrs_1668696182826/work
certifi @ file:///croot/certifi_1671487769961/work/certifi
charset-normalizer==3.4.1
coverage==7.2.7
ddt==1.7.2
-e git+https://github.com/falconry/falcon.git@a8154de497b3ec5d6e5579026e74e9073e353819#egg=falcon
flit_core @ file:///opt/conda/conda-bld/flit-core_1644941570762/work/source/flit_core
idna==3.10
importlib-metadata @ file:///tmp/build/80754af9/importlib-metadata_1648562407465/work
iniconfig @ file:///home/linux1/recipes/ci/iniconfig_1610983019677/work
nose==1.3.7
packaging @ file:///croot/packaging_1671697413597/work
pluggy @ file:///tmp/build/80754af9/pluggy_1648042572264/work
py @ file:///opt/conda/conda-bld/py_1644396412707/work
pytest==7.1.2
python-mimeparse==1.6.0
PyYAML==6.0.1
requests==2.31.0
six==1.17.0
testtools==2.7.1
tomli @ file:///opt/conda/conda-bld/tomli_1657175507142/work
typing_extensions @ file:///croot/typing_extensions_1669924550328/work
urllib3==2.0.7
zipp @ file:///croot/zipp_1672387121353/work

name: falcon
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - attrs=22.1.0=py37h06a4308_0
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2022.12.7=py37h06a4308_0
  - flit-core=3.6.0=pyhd3eb1b0_0
  - importlib-metadata=4.11.3=py37h06a4308_0
  - importlib_metadata=4.11.3=hd3eb1b0_0
  - iniconfig=1.1.1=pyhd3eb1b0_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - packaging=22.0=py37h06a4308_0
  - pip=22.3.1=py37h06a4308_0
  - pluggy=1.0.0=py37h06a4308_1
  - py=1.11.0=pyhd3eb1b0_0
  - pytest=7.1.2=py37h06a4308_0
  - python=3.7.16=h7a1cb2a_0
  - readline=8.2=h5eee18b_0
  - setuptools=65.6.3=py37h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tomli=2.0.1=py37h06a4308_0
  - typing_extensions=4.4.0=py37h06a4308_0
  - wheel=0.38.4=py37h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zipp=3.11.0=py37h06a4308_0
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - charset-normalizer==3.4.1
      - coverage==7.2.7
      - ddt==1.7.2
      - idna==3.10
      - nose==1.3.7
      - python-mimeparse==1.6.0
      - pyyaml==6.0.1
      - requests==2.31.0
      - six==1.17.0
      - testtools==2.7.1
      - urllib3==2.0.7
prefix: /opt/conda/envs/falcon

tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_in_process_resource tests/test_middlewares.py::TestRequestTimeMiddleware::test_log_get_request tests/test_middlewares.py::TestSeveralMiddlewares::test_generate_trans_id_and_time_with_request tests/test_middlewares.py::TestSeveralMiddlewares::test_middleware_execution_order tests/test_middlewares.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_resp tests/test_middlewares.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_rsrc tests/test_middlewares.py::TestResourceMiddleware::test_can_access_resource_params
tests/test_httpstatus.py::TestHTTPStatus::test_raise_status_empty_body tests/test_httpstatus.py::TestHTTPStatus::test_raise_status_in_before_hook tests/test_httpstatus.py::TestHTTPStatus::test_raise_status_in_responder tests/test_httpstatus.py::TestHTTPStatus::test_raise_status_runs_after_hooks tests/test_httpstatus.py::TestHTTPStatus::test_raise_status_survives_after_hooks tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_in_before_hook tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_in_process_request tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_runs_after_hooks tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_runs_process_response tests/test_httpstatus.py::TestHTTPStatusWithGlobalHooks::test_raise_status_survives_after_hooks tests/test_middlewares.py::TestRequestTimeMiddleware::test_add_invalid_middleware tests/test_middlewares.py::TestRequestTimeMiddleware::test_response_middleware_raises_exception tests/test_middlewares.py::TestTransactionIdMiddleware::test_generate_trans_id_with_request tests/test_middlewares.py::TestSeveralMiddlewares::test_inner_mw_throw_exception tests/test_middlewares.py::TestSeveralMiddlewares::test_inner_mw_with_ex_handler_throw_exception tests/test_middlewares.py::TestSeveralMiddlewares::test_order_mw_executed_when_exception_in_req tests/test_middlewares.py::TestSeveralMiddlewares::test_outer_mw_with_ex_handler_throw_exception tests/test_middlewares.py::TestRemoveBasePathMiddleware::test_base_path_is_removed_before_routing
falcon/api.py
falcon/api.py