diff --git a/jwt.info.yml b/jwt.info.yml
index 8437f61..f140eec 100644
--- a/jwt.info.yml
+++ b/jwt.info.yml
@@ -2,7 +2,7 @@ name: JSON Web Token Authentication (JWT)
 description: Provides functionality for issuing, validating, and authenticating with JSON Web Tokens.
 package: Web services
 type: module
-core_version_requirement: ^10.2 || ^11
+core_version_requirement: ^10.2 || ^11 || ^12
 configure: jwt.jwt_config_form
 dependencies:
   - key:key
diff --git a/jwt.install b/jwt.install
index 8da1f67..e9668a0 100644
--- a/jwt.install
+++ b/jwt.install
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
+
 /**
  * @file
  * Install, update, and uninstall functions for the jwt module.
@@ -14,7 +21,7 @@ function jwt_requirements($phase) {
     if (!class_exists('\Firebase\JWT\JWT')) {
       $requirements['jwt_library'] = [
         'description' => t('JWT Authentication requires the firebase/php-jwt library.'),
-        'severity' => REQUIREMENT_ERROR,
+        'severity' => DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::Error, fn() => REQUIREMENT_ERROR),
       ];
     }
   }
diff --git a/modules/jwt_auth_consumer/jwt_auth_consumer.info.yml b/modules/jwt_auth_consumer/jwt_auth_consumer.info.yml
index 2f684d2..f30bb11 100644
--- a/modules/jwt_auth_consumer/jwt_auth_consumer.info.yml
+++ b/modules/jwt_auth_consumer/jwt_auth_consumer.info.yml
@@ -1,7 +1,7 @@
 name: JWT Authentication Consumer
 type: module
 description: Authenticates JWTs generated by the jwt_auth_issuer module.
-core_version_requirement: ^10.2 || ^11
+core_version_requirement: ^10.2 || ^11 || ^12
 package: Web services
 dependencies:
   - jwt:jwt
diff --git a/modules/jwt_auth_consumer/jwt_auth_consumer.module b/modules/jwt_auth_consumer/jwt_auth_consumer.module
index 0929dbe..6bca65d 100644
--- a/modules/jwt_auth_consumer/jwt_auth_consumer.module
+++ b/modules/jwt_auth_consumer/jwt_auth_consumer.module
@@ -5,18 +5,14 @@
  * Contains jwt_auth_consumer.module..
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\jwt_auth_consumer\Hook\JwtAuthConsumerHooks;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function jwt_auth_consumer_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    // Main module help for the jwt_auth_consumer module.
-    case 'help.page.jwt_auth_consumer':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Authenticates JWTs generated by the jwt_auth_issuer module.') . '</p>';
-      return $output;
-  }
+  return \Drupal::service(JwtAuthConsumerHooks::class)->help($route_name, $route_match);
 }
diff --git a/modules/jwt_auth_consumer/jwt_auth_consumer.services.yml b/modules/jwt_auth_consumer/jwt_auth_consumer.services.yml
index 4417787..1a03c3d 100644
--- a/modules/jwt_auth_consumer/jwt_auth_consumer.services.yml
+++ b/modules/jwt_auth_consumer/jwt_auth_consumer.services.yml
@@ -4,3 +4,7 @@ services:
     autowire: true
   jwt_auth_consumer.subscriber:
     class: Drupal\jwt_auth_consumer\EventSubscriber\JwtAuthConsumerSubscriber
+
+  Drupal\jwt_auth_consumer\Hook\JwtAuthConsumerHooks:
+    class: Drupal\jwt_auth_consumer\Hook\JwtAuthConsumerHooks
+    autowire: true
diff --git a/modules/jwt_auth_consumer/src/Hook/JwtAuthConsumerHooks.php b/modules/jwt_auth_consumer/src/Hook/JwtAuthConsumerHooks.php
new file mode 100644
index 0000000..811de70
--- /dev/null
+++ b/modules/jwt_auth_consumer/src/Hook/JwtAuthConsumerHooks.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\jwt_auth_consumer\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for jwt_auth_consumer.
+ */
+class JwtAuthConsumerHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      // Main module help for the jwt_auth_consumer module.
+      case 'help.page.jwt_auth_consumer':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('Authenticates JWTs generated by the jwt_auth_issuer module.') . '</p>';
+        return $output;
+    }
+  }
+
+}
diff --git a/modules/jwt_auth_issuer/jwt_auth_issuer.info.yml b/modules/jwt_auth_issuer/jwt_auth_issuer.info.yml
index 319d8ae..07b6d2f 100644
--- a/modules/jwt_auth_issuer/jwt_auth_issuer.info.yml
+++ b/modules/jwt_auth_issuer/jwt_auth_issuer.info.yml
@@ -1,7 +1,7 @@
 name: JWT Authentication Issuer
 type: module
 description: Provides an endpoint which will issue JWTs.
-core_version_requirement: ^10.2 || ^11
+core_version_requirement: ^10.2 || ^11 || ^12
 configure: jwt.jwt_config_form
 package: Web services
 dependencies:
diff --git a/modules/jwt_auth_issuer/jwt_auth_issuer.install b/modules/jwt_auth_issuer/jwt_auth_issuer.install
index 28850b0..9f3e514 100644
--- a/modules/jwt_auth_issuer/jwt_auth_issuer.install
+++ b/modules/jwt_auth_issuer/jwt_auth_issuer.install
@@ -1,5 +1,11 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Component\Utility\DeprecationHelper;
+
 /**
  * @file
  * Install, update and uninstall functions for the JWT Auth issuer module.
@@ -9,8 +15,10 @@
  * Add jwt_auth_issuer.config to the system.
  */
 function jwt_auth_issuer_update_20001() {
-  \Drupal::configFactory()
+  DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::configFactory()
+    ->getEditable('jwt_auth_issuer.config')
+    ->set('jwt_in_login_response', FALSE)->save(), fn() => \Drupal::configFactory()
     ->getEditable('jwt_auth_issuer.config')
     ->set('jwt_in_login_response', FALSE)
-    ->save(TRUE);
+    ->save(TRUE));
 }
diff --git a/modules/jwt_auth_issuer/jwt_auth_issuer.module b/modules/jwt_auth_issuer/jwt_auth_issuer.module
index 74e09b4..faf8d9d 100644
--- a/modules/jwt_auth_issuer/jwt_auth_issuer.module
+++ b/modules/jwt_auth_issuer/jwt_auth_issuer.module
@@ -5,38 +5,23 @@
  * Contains jwt_auth_issuer.module..
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\jwt_auth_issuer\Hook\JwtAuthIssuerHooks;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function jwt_auth_issuer_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    // Main module help for the jwt_auth_issuer module.
-    case 'help.page.jwt_auth_issuer':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Provides an endpoint which will issue JWTs.') . '</p>';
-      return $output;
-  }
+  return \Drupal::service(JwtAuthIssuerHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_form_FORM_ID_alter().
  */
+#[LegacyHook]
 function jwt_auth_issuer_form_jwt_config_form_alter(&$form, FormStateInterface $form_state) {
-  $form['jwt_auth_issuer'] = [
-    '#type' => 'details',
-    '#title' => t('JWT Auth issuer settings'),
-    '#weight' => 20,
-    '#tree' => TRUE,
-    '#open' => TRUE,
-  ];
-  $form['jwt_auth_issuer']['jwt_in_login_response'] = [
-    '#type' => 'checkbox',
-    '#config_target' => 'jwt_auth_issuer.config:jwt_in_login_response',
-    '#title' => t('Include a JWT token in the user login response.'),
-    '#weight' => 20,
-  ];
+  \Drupal::service(JwtAuthIssuerHooks::class)->formJwtConfigFormAlter($form, $form_state);
 }
diff --git a/modules/jwt_auth_issuer/jwt_auth_issuer.services.yml b/modules/jwt_auth_issuer/jwt_auth_issuer.services.yml
index a8092c6..a60bdd1 100644
--- a/modules/jwt_auth_issuer/jwt_auth_issuer.services.yml
+++ b/modules/jwt_auth_issuer/jwt_auth_issuer.services.yml
@@ -6,3 +6,7 @@ services:
     class: Drupal\jwt_auth_issuer\EventSubscriber\JwtAuthIssuerSubscriber
   jwt_auth_issuer.login_listener:
     class: Drupal\jwt_auth_issuer\EventSubscriber\JwtLoginSubscriber
+
+  Drupal\jwt_auth_issuer\Hook\JwtAuthIssuerHooks:
+    class: Drupal\jwt_auth_issuer\Hook\JwtAuthIssuerHooks
+    autowire: true
diff --git a/modules/jwt_auth_issuer/src/Hook/JwtAuthIssuerHooks.php b/modules/jwt_auth_issuer/src/Hook/JwtAuthIssuerHooks.php
new file mode 100644
index 0000000..146f70f
--- /dev/null
+++ b/modules/jwt_auth_issuer/src/Hook/JwtAuthIssuerHooks.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Drupal\jwt_auth_issuer\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for jwt_auth_issuer.
+ */
+class JwtAuthIssuerHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      // Main module help for the jwt_auth_issuer module.
+      case 'help.page.jwt_auth_issuer':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('Provides an endpoint which will issue JWTs.') . '</p>';
+        return $output;
+    }
+  }
+
+  /**
+   * Implements hook_form_FORM_ID_alter().
+   */
+  #[Hook('form_jwt_config_form_alter')]
+  public function formJwtConfigFormAlter(&$form, FormStateInterface $form_state) {
+    $form['jwt_auth_issuer'] = [
+      '#type' => 'details',
+      '#title' => $this->t('JWT Auth issuer settings'),
+      '#weight' => 20,
+      '#tree' => TRUE,
+      '#open' => TRUE,
+    ];
+    $form['jwt_auth_issuer']['jwt_in_login_response'] = [
+      '#type' => 'checkbox',
+      '#config_target' => 'jwt_auth_issuer.config:jwt_in_login_response',
+      '#title' => $this->t('Include a JWT token in the user login response.'),
+      '#weight' => 20,
+    ];
+  }
+
+}
diff --git a/modules/jwt_path_auth/jwt_path_auth.info.yml b/modules/jwt_path_auth/jwt_path_auth.info.yml
index 7472996..9ec379e 100644
--- a/modules/jwt_path_auth/jwt_path_auth.info.yml
+++ b/modules/jwt_path_auth/jwt_path_auth.info.yml
@@ -1,7 +1,7 @@
 name: JWT Path Authentication
 type: module
 description: Authenticate using JWT in the query string applicable to a path.
-core_version_requirement: ^10.2 || ^11
+core_version_requirement: ^10.2 || ^11 || ^12
 package: Web services
 dependencies:
   - jwt:jwt
diff --git a/modules/users_jwt/src/Hook/UsersJwtHooks.php b/modules/users_jwt/src/Hook/UsersJwtHooks.php
new file mode 100644
index 0000000..84562ad
--- /dev/null
+++ b/modules/users_jwt/src/Hook/UsersJwtHooks.php
@@ -0,0 +1,32 @@
+<?php
+
+namespace Drupal\users_jwt\Hook;
+
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for users_jwt.
+ */
+class UsersJwtHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public function help($route_name, RouteMatchInterface $route_match) {
+    switch ($route_name) {
+      // Main module help for the users_jwt module.
+      case 'help.page.users_jwt':
+        $output = '';
+        $output .= '<h3>' . $this->t('About') . '</h3>';
+        $output .= '<p>' . $this->t('Manage one or more RSA public keys per user for JWT authentication') . '</p>';
+        return $output;
+
+      default:
+    }
+  }
+
+}
diff --git a/modules/users_jwt/users_jwt.info.yml b/modules/users_jwt/users_jwt.info.yml
index 5f0c940..2796939 100644
--- a/modules/users_jwt/users_jwt.info.yml
+++ b/modules/users_jwt/users_jwt.info.yml
@@ -1,5 +1,5 @@
 name: "Users' JWT Authentication"
 type: module
 description: Manage one or more RSA public keys per user for JWT authentication
-core_version_requirement: ^10.2 || ^11
+core_version_requirement: ^10.2 || ^11 || ^12
 package: Web services
diff --git a/modules/users_jwt/users_jwt.module b/modules/users_jwt/users_jwt.module
index 41b03f3..acf897f 100644
--- a/modules/users_jwt/users_jwt.module
+++ b/modules/users_jwt/users_jwt.module
@@ -5,20 +5,14 @@
  * Contains users_jwt.module.
  */
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\users_jwt\Hook\UsersJwtHooks;
 use Drupal\Core\Routing\RouteMatchInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function users_jwt_help($route_name, RouteMatchInterface $route_match) {
-  switch ($route_name) {
-    // Main module help for the users_jwt module.
-    case 'help.page.users_jwt':
-      $output = '';
-      $output .= '<h3>' . t('About') . '</h3>';
-      $output .= '<p>' . t('Manage one or more RSA public keys per user for JWT authentication') . '</p>';
-      return $output;
-
-    default:
-  }
+  return \Drupal::service(UsersJwtHooks::class)->help($route_name, $route_match);
 }
diff --git a/modules/users_jwt/users_jwt.services.yml b/modules/users_jwt/users_jwt.services.yml
index 860c256..c92974f 100644
--- a/modules/users_jwt/users_jwt.services.yml
+++ b/modules/users_jwt/users_jwt.services.yml
@@ -15,3 +15,7 @@ services:
   users_jwt.memory_cache:
     class: Drupal\Core\Cache\MemoryCache\MemoryCache
     arguments: ['@datetime.time']
+
+  Drupal\users_jwt\Hook\UsersJwtHooks:
+    class: Drupal\users_jwt\Hook\UsersJwtHooks
+    autowire: true
diff --git a/tests/modules/jwt_test/src/EventSubscriber/JwtTestAuthIssuerSubscriber.php b/tests/modules/jwt_test/src/EventSubscriber/JwtTestAuthIssuerSubscriber.php
index d3f513c..af61145 100644
--- a/tests/modules/jwt_test/src/EventSubscriber/JwtTestAuthIssuerSubscriber.php
+++ b/tests/modules/jwt_test/src/EventSubscriber/JwtTestAuthIssuerSubscriber.php
@@ -21,7 +21,7 @@ class JwtTestAuthIssuerSubscriber implements EventSubscriberInterface {
   /**
    * {@inheritdoc}
    */
-  public static function getSubscribedEvents() {
+  public static function getSubscribedEvents(): array {
     $events = [];
     $events[JwtAuthEvents::GENERATE][] = ['modifyStandardClaims', 20];
     return $events;
