diff --git a/src/EventSubscriber/TfaOneTimeLoginEventSubscriber.php b/src/EventSubscriber/TfaOneTimeLoginEventSubscriber.php
index e78eb59..01bc07a 100644
--- a/src/EventSubscriber/TfaOneTimeLoginEventSubscriber.php
+++ b/src/EventSubscriber/TfaOneTimeLoginEventSubscriber.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\tfa\EventSubscriber;
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\user\OneTimeAuthentication;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Utility\Crypt;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -123,7 +125,7 @@ final class TfaOneTimeLoginEventSubscriber implements EventSubscriberInterface {
       return;
     }
 
-    if (($timestamp < $user->getLastLoginTime()) || ($timestamp > $current) || !hash_equals($hash, user_pass_rehash($user, $timestamp))) {
+    if (($timestamp < $user->getLastLoginTime()) || ($timestamp > $current) || !hash_equals($hash, DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.4.0', fn() => \Drupal::service(OneTimeAuthentication::class)->generateHmac($user, $timestamp), fn() => user_pass_rehash($user, $timestamp)))) {
       $this->messenger->addError($this->t('You have tried to use a one-time login link that has either been used or is no longer valid. Please request a new one using the form below.'));
       $this->setRedirectToUserPassPage($event);
       return;
diff --git a/src/Hook/TfaHooks.php b/src/Hook/TfaHooks.php
new file mode 100644
index 0000000..57e4613
--- /dev/null
+++ b/src/Hook/TfaHooks.php
@@ -0,0 +1,128 @@
+<?php
+
+namespace Drupal\tfa\Hook;
+
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\Core\Session\AccountInterface;
+use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Url;
+use Drupal\user\UserInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Routing\RouteMatchInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for tfa.
+ */
+class TfaHooks {
+  use StringTranslationTrait;
+
+  /**
+   * Implements hook_help().
+   */
+  #[Hook('help')]
+  public static function help(string $route_name, RouteMatchInterface $route_match): string {
+    $output = '';
+    switch ($route_name) {
+      // Main module help for the tfa module.
+      case 'help.page.tfa':
+        $output .= '<h3>' . \Drupal::translation()->translate('About') . '</h3>';
+        $output .= '<p>' . \Drupal::translation()->translate('Pluggable provider of second factor authentication for Drupal. For more information, see the online documentation for the <a href=":tfa">Two-factor Authentication</a> module.', [
+          ':tfa' => 'https://www.drupal.org/project/tfa',
+        ]) . '</p>';
+    }
+    return $output;
+  }
+
+  /**
+   * Implements hook_entity_operation().
+   */
+  #[Hook('entity_operation')]
+  public function entityOperation(EntityInterface $entity): array {
+    $operations = [];
+    if ($entity instanceof UserInterface) {
+      $url = Url::fromRoute('tfa.overview', [
+        'user' => $entity->id(),
+      ]);
+      if ($url->access() === TRUE) {
+        $operations['tfa'] = [
+          'title' => $this->t('TFA'),
+          'url' => $url,
+          'weight' => 50,
+        ];
+      }
+    }
+    return $operations;
+  }
+
+  /**
+   * Implements hook_mail().
+   */
+  #[Hook('mail')]
+  public static function mail(string $key, array &$message, array $params): void {
+    $token_service = \Drupal::token();
+    $language_manager = \Drupal::languageManager();
+    $variables = [
+      'user' => $params['account'],
+    ];
+    $language = $language_manager->getLanguage($params['account']->getPreferredLangcode());
+    $original_language = $language_manager->getConfigOverrideLanguage();
+    $language_manager->setConfigOverrideLanguage($language);
+    $tfa_config = \Drupal::config('tfa.settings');
+    $token_options = [
+      'langcode' => $message['langcode'],
+      'clear' => TRUE,
+    ];
+    // Configuration mapping key matches the hook_mail() $key.
+    $subject = $tfa_config->get("mail.{$key}.subject");
+    $subject = $token_service->replace($subject, $variables, $token_options);
+    $message['subject'] = PlainTextOutput::renderFromHtml($subject);
+    $body = $tfa_config->get("mail.{$key}.body");
+    $message['body'][] = $token_service->replace($body, $variables, $token_options);
+    $language_manager->setConfigOverrideLanguage($original_language);
+  }
+
+  /**
+   * Implements hook_user_login().
+   */
+  #[Hook('user_login')]
+  public static function userLogin(UserInterface $account): void {
+    /** @var \Drupal\Core\Cache\CacheBackendInterface $memory_cache */
+    $memory_cache = \Drupal::service('cache.tfa_memcache');
+    /** @var FALSE|object{'data': mixed} $tfa_complete_this_request */
+    $tfa_complete_this_request = $memory_cache->get('tfa_complete');
+    if ($tfa_complete_this_request !== FALSE) {
+      $user_auth_as_id = $tfa_complete_this_request->data;
+      if (is_int($user_auth_as_id) && $user_auth_as_id === (int) $account->id()) {
+        /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
+        $session = \Drupal::service('session');
+        $session->set('tfa_complete', (int) $account->id());
+      }
+    }
+  }
+
+  /**
+   * Implements hook_user_logout().
+   */
+  #[Hook('user_logout')]
+  public static function userLogout(AccountInterface $account): void {
+    /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
+    $session = \Drupal::service('session');
+    $session->remove('tfa_complete');
+  }
+
+  /**
+   * Implements hook_form_alter().
+   */
+  #[Hook('form_alter')]
+  public static function formAlter(array &$form, FormStateInterface &$form_state, string $form_id): void {
+    switch ($form_id) {
+      case 'user_login_form':
+      case 'user_login_block':
+        \Drupal::service('tfa.login_form_helper')->alterLoginForm($form);
+        break;
+    }
+  }
+
+}
diff --git a/src/Hook/TfaViewsHooks.php b/src/Hook/TfaViewsHooks.php
new file mode 100644
index 0000000..4afcd29
--- /dev/null
+++ b/src/Hook/TfaViewsHooks.php
@@ -0,0 +1,34 @@
+<?php
+
+namespace Drupal\tfa\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+use Drupal\Core\StringTranslation\StringTranslationTrait;
+
+/**
+ * Hook implementations for tfa.
+ */
+class TfaViewsHooks {
+  use StringTranslationTrait;
+  /**
+   * @file
+   * TFA views configuration.
+   */
+
+  /**
+   * Implements hook_views_data_alter().
+   */
+  #[Hook('views_data_alter')]
+  public function viewsDataAlter(array &$data): void {
+    $data['users']['tfa_enabled_field'] = [
+      'title' => $this->t('TFA enabled'),
+      'real field' => 'uid',
+      'field' => [
+        'title' => $this->t('TFA enabled'),
+        'help' => $this->t('Whether the user has enabled two-factor authentication.'),
+        'id' => 'tfa_enabled_field',
+      ],
+    ];
+  }
+
+}
diff --git a/tests/src/Functional/TfaConfigTest.php b/tests/src/Functional/TfaConfigTest.php
index 4c7bc0e..d2be4f9 100644
--- a/tests/src/Functional/TfaConfigTest.php
+++ b/tests/src/Functional/TfaConfigTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\user\Entity\User;
 
 /**
@@ -9,6 +11,8 @@ use Drupal\user\Entity\User;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaConfigTest extends TfaTestBase {
   /**
    * User doing the TFA Validation.
diff --git a/tests/src/Functional/TfaEncryptionSetupTest.php b/tests/src/Functional/TfaEncryptionSetupTest.php
index bf17b06..9e65538 100644
--- a/tests/src/Functional/TfaEncryptionSetupTest.php
+++ b/tests/src/Functional/TfaEncryptionSetupTest.php
@@ -4,6 +4,9 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Url;
 use Drupal\tfa_test_encryption\Plugin\EncryptionMethod\FailingEncryption;
@@ -13,6 +16,8 @@ use Drupal\tfa_test_encryption\Plugin\EncryptionMethod\FailingEncryption;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 final class TfaEncryptionSetupTest extends TfaTestBase {
 
   /**
@@ -43,6 +48,7 @@ final class TfaEncryptionSetupTest extends TfaTestBase {
    *
    * @dataProvider providerEncryptionOnSetup
    */
+  #[DataProvider('providerEncryptionOnSetup')]
   public function testEncryptionOnSetup(string $encryptionPluginId, string $expectMessageAfterSetup): void {
     // Set up the basics programmatically. Speeds things up, and we're not
     // concerned about configuration UI.
diff --git a/tests/src/Functional/TfaFunctionalServicesTest.php b/tests/src/Functional/TfaFunctionalServicesTest.php
index 80e2f6c..8adb178 100644
--- a/tests/src/Functional/TfaFunctionalServicesTest.php
+++ b/tests/src/Functional/TfaFunctionalServicesTest.php
@@ -4,12 +4,15 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\Cache\MemoryBackend;
 use Drupal\Tests\BrowserTestBase;
 
 /**
  * Tests the TFA Memory Cache service.
  */
+#[RunTestsInSeparateProcesses]
 final class TfaFunctionalServicesTest extends BrowserTestBase {
 
   /**
@@ -32,6 +35,7 @@ final class TfaFunctionalServicesTest extends BrowserTestBase {
    *
    * @group tfa
    */
+  #[Group('tfa')]
   public function testTfaMemoryCache():void {
     $service = \Drupal::service('cache.tfa_memcache');
     $this->assertInstanceOf(MemoryBackend::class, $service, 'cache.tfa_memcache must be backed by the memory backend.');
diff --git a/tests/src/Functional/TfaHotpSetupPluginTest.php b/tests/src/Functional/TfaHotpSetupPluginTest.php
index 34a60b4..afce899 100644
--- a/tests/src/Functional/TfaHotpSetupPluginTest.php
+++ b/tests/src/Functional/TfaHotpSetupPluginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\Plugin\Tfa\TfaHotp;
 use Drupal\user\Entity\User;
 use OTPHP\HOTP;
@@ -11,6 +13,8 @@ use OTPHP\HOTP;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaHotpSetupPluginTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/TfaHotpValidationPluginTest.php b/tests/src/Functional/TfaHotpValidationPluginTest.php
index 06e5c85..828a858 100644
--- a/tests/src/Functional/TfaHotpValidationPluginTest.php
+++ b/tests/src/Functional/TfaHotpValidationPluginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\Plugin\Tfa\TfaHotp;
 use Drupal\user\Entity\User;
 use OTPHP\HOTP;
@@ -11,6 +13,8 @@ use OTPHP\HOTP;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaHotpValidationPluginTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/TfaLoginControllerTest.php b/tests/src/Functional/TfaLoginControllerTest.php
index 85b88d9..1bf41ca 100644
--- a/tests/src/Functional/TfaLoginControllerTest.php
+++ b/tests/src/Functional/TfaLoginControllerTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Url;
 use Drupal\tfa_test_user\Entity\TfaTestUser;
 
@@ -13,6 +15,8 @@ use Drupal\tfa_test_user\Entity\TfaTestUser;
  * @group tfa
  * @coversDefaultClass \Drupal\tfa\Controller\TfaLoginController
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 final class TfaLoginControllerTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/TfaLoginTest.php b/tests/src/Functional/TfaLoginTest.php
index 9d2d039..0f3ea6b 100644
--- a/tests/src/Functional/TfaLoginTest.php
+++ b/tests/src/Functional/TfaLoginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\TfaUserDataTrait;
 use Drupal\tfa_test_plugins\Plugin\Tfa\TfaTestLoginPlugin;
 use Drupal\user\Entity\User;
@@ -12,6 +14,8 @@ use Drupal\user\RoleInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaLoginTest extends TfaTestBase {
   use TfaUserDataTrait;
 
diff --git a/tests/src/Functional/TfaPasswordResetTest.php b/tests/src/Functional/TfaPasswordResetTest.php
index 5662e39..365e5f2 100644
--- a/tests/src/Functional/TfaPasswordResetTest.php
+++ b/tests/src/Functional/TfaPasswordResetTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Test\AssertMailTrait;
 use Drupal\Tests\WebAssert;
 use Drupal\tfa\TfaUserDataTrait;
@@ -14,6 +16,8 @@ use Drupal\user\Entity\User;
  *
  * @group Tfa
  */
+#[Group('Tfa')]
+#[RunTestsInSeparateProcesses]
 final class TfaPasswordResetTest extends TfaTestBase {
 
   use AssertMailTrait {
diff --git a/tests/src/Functional/TfaRecoveryCodePluginTest.php b/tests/src/Functional/TfaRecoveryCodePluginTest.php
index d130135..af85065 100644
--- a/tests/src/Functional/TfaRecoveryCodePluginTest.php
+++ b/tests/src/Functional/TfaRecoveryCodePluginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\Plugin\Tfa\TfaRecoveryCode;
 use Drupal\tfa\TfaPluginManager;
 use Drupal\user\UserInterface;
@@ -13,6 +15,8 @@ use Drupal\user\UserInterface;
  *
  * @ingroup tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaRecoveryCodePluginTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/TfaTotpSetupPluginTest.php b/tests/src/Functional/TfaTotpSetupPluginTest.php
index 9fe13e7..36dd411 100644
--- a/tests/src/Functional/TfaTotpSetupPluginTest.php
+++ b/tests/src/Functional/TfaTotpSetupPluginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\Plugin\Tfa\TfaTotp;
 use Drupal\user\Entity\User;
 use OTPHP\TOTP;
@@ -11,6 +13,8 @@ use OTPHP\TOTP;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaTotpSetupPluginTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/TfaTotpValidationPluginTest.php b/tests/src/Functional/TfaTotpValidationPluginTest.php
index 7d9d984..3e27a37 100644
--- a/tests/src/Functional/TfaTotpValidationPluginTest.php
+++ b/tests/src/Functional/TfaTotpValidationPluginTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Functional;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\tfa\Plugin\Tfa\TfaTotp;
 use Drupal\user\Entity\User;
 use OTPHP\TOTP;
@@ -11,6 +13,8 @@ use OTPHP\TOTP;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaTotpValidationPluginTest extends TfaTestBase {
 
   /**
diff --git a/tests/src/Functional/UpdateTests/SetTotpTimeWindowFromAcceptedCodesTest.php b/tests/src/Functional/UpdateTests/SetTotpTimeWindowFromAcceptedCodesTest.php
index 6d616c9..e56f0b4 100644
--- a/tests/src/Functional/UpdateTests/SetTotpTimeWindowFromAcceptedCodesTest.php
+++ b/tests/src/Functional/UpdateTests/SetTotpTimeWindowFromAcceptedCodesTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\tfa\Functional\UpdateTests;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Test basic E2E operation of TOTP time window conversion post_update hook.
  *
@@ -9,6 +12,8 @@ namespace Drupal\Tests\tfa\Functional\UpdateTests;
  *
  * @covers ::tfa_post_update_convert_totp_from_accepted_codes
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class SetTotpTimeWindowFromAcceptedCodesTest extends UpdateTestBase {
 
   /**
diff --git a/tests/src/Functional/UpdateTests/TfaUserLoginBlockRemovedTest.php b/tests/src/Functional/UpdateTests/TfaUserLoginBlockRemovedTest.php
index 40a048e..6ed8dae 100644
--- a/tests/src/Functional/UpdateTests/TfaUserLoginBlockRemovedTest.php
+++ b/tests/src/Functional/UpdateTests/TfaUserLoginBlockRemovedTest.php
@@ -2,6 +2,9 @@
 
 namespace Drupal\Tests\tfa\Functional\UpdateTests;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
+
 /**
  * Test basic E2E operation of login block plugin conversion post_update hook.
  *
@@ -9,6 +12,8 @@ namespace Drupal\Tests\tfa\Functional\UpdateTests;
  *
  * @coversNothing
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaUserLoginBlockRemovedTest extends UpdateTestBase {
 
   /**
diff --git a/tests/src/Kernel/TfaLoginContextFactoryTest.php b/tests/src/Kernel/TfaLoginContextFactoryTest.php
index fc6f8bc..6a5baa5 100644
--- a/tests/src/Kernel/TfaLoginContextFactoryTest.php
+++ b/tests/src/Kernel/TfaLoginContextFactoryTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\user\UserInterface;
 
@@ -10,6 +12,8 @@ use Drupal\user\UserInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaLoginContextFactoryTest extends KernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/TfaPluginManagerTest.php b/tests/src/Kernel/TfaPluginManagerTest.php
index 7c81724..1ab5df0 100644
--- a/tests/src/Kernel/TfaPluginManagerTest.php
+++ b/tests/src/Kernel/TfaPluginManagerTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Kernel;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\tfa\TfaPluginManager;
 use PHPUnit\Framework\Attributes\CoversClass;
@@ -18,6 +19,7 @@ use PHPUnit\Framework\Attributes\Group;
  */
 #[Group('tfa')]
 #[CoversClass(TfaPluginManager::class)]
+#[RunTestsInSeparateProcesses]
 final class TfaPluginManagerTest extends KernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/TfaServicesTest.php b/tests/src/Kernel/TfaServicesTest.php
index c0f4943..3d173b8 100644
--- a/tests/src/Kernel/TfaServicesTest.php
+++ b/tests/src/Kernel/TfaServicesTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Kernel;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\KernelTestBase;
 use Drupal\Tests\user\Traits\UserCreationTrait;
 use Drupal\tfa\TfaUserAuth;
@@ -14,6 +16,8 @@ use Symfony\Component\DependencyInjection\Reference;
  *
  * @coversNothing
  */
+#[Group('tfa')]
+#[RunTestsInSeparateProcesses]
 class TfaServicesTest extends KernelTestBase {
 
   use UserCreationTrait;
@@ -48,7 +52,6 @@ class TfaServicesTest extends KernelTestBase {
 
     // Setup for an E2E test of the decorator.
     $this->installEntitySchema('user');
-    $this->installSchema('system', ['sequences']);
     $this->installSchema('user', ['users_data']);
     $tfa_required_role = $this->createRole([]);
     $this->assertNotFalse($tfa_required_role);
diff --git a/tests/src/Unit/Authentication/TfaAuthDecoratorTest.php b/tests/src/Unit/Authentication/TfaAuthDecoratorTest.php
index 8e66dc4..d9a33b6 100644
--- a/tests/src/Unit/Authentication/TfaAuthDecoratorTest.php
+++ b/tests/src/Unit/Authentication/TfaAuthDecoratorTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -19,6 +21,7 @@ use Symfony\Component\HttpFoundation\Request;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaAuthDecoratorTest extends UnitTestCase {
 
   /**
@@ -81,6 +84,7 @@ class TfaAuthDecoratorTest extends UnitTestCase {
    *
    * @dataProvider providerTestAuthenticate
    */
+  #[DataProvider('providerTestAuthenticate')]
   public function testAuthenticate(bool $return_user, int $expect_cache_call): void {
 
     if ($return_user) {
diff --git a/tests/src/Unit/Authentication/TfaChallengeAuthDecoratorTest.php b/tests/src/Unit/Authentication/TfaChallengeAuthDecoratorTest.php
index dd5adaa..92a3e5b 100644
--- a/tests/src/Unit/Authentication/TfaChallengeAuthDecoratorTest.php
+++ b/tests/src/Unit/Authentication/TfaChallengeAuthDecoratorTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
@@ -21,6 +23,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaChallengeAuthDecoratorTest extends UnitTestCase {
 
   /**
@@ -83,6 +86,7 @@ class TfaChallengeAuthDecoratorTest extends UnitTestCase {
    *
    * @dataProvider providerTestAuthenticate
    */
+  #[DataProvider('providerTestAuthenticate')]
   public function testAuthenticate(bool $return_user, int $expect_cache_call): void {
 
     if ($return_user) {
@@ -134,6 +138,7 @@ class TfaChallengeAuthDecoratorTest extends UnitTestCase {
    *
    * @dataProvider providerTestChallengeException
    */
+  #[DataProvider('providerTestChallengeException')]
   public function testChallengeException(?HttpExceptionInterface $inner_return): void {
     $request_mock = new Request();
     $exception_mock = new \Exception();
diff --git a/tests/src/Unit/Compiler/TfaAuthDecoratorCompilerTest.php b/tests/src/Unit/Compiler/TfaAuthDecoratorCompilerTest.php
index 50193ba..56bcb21 100644
--- a/tests/src/Unit/Compiler/TfaAuthDecoratorCompilerTest.php
+++ b/tests/src/Unit/Compiler/TfaAuthDecoratorCompilerTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\Compiler;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Authentication\AuthenticationProviderChallengeInterface;
 use Drupal\Core\Authentication\AuthenticationProviderInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
@@ -24,6 +26,7 @@ use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaAuthDecoratorCompilerTest extends UnitTestCase {
 
   /**
@@ -46,6 +49,7 @@ class TfaAuthDecoratorCompilerTest extends UnitTestCase {
    *
    * @dataProvider providerProcess
    */
+  #[DataProvider('providerProcess')]
   public function testProcess(mixed $bypass_list, mixed $priority, callable $setup): void {
     $provider = new TfaAuthDecoratorCompiler();
     $this->containerBuilderMock
diff --git a/tests/src/Unit/Drush/TfaTokenManagementTest.php b/tests/src/Unit/Drush/TfaTokenManagementTest.php
index d0ee125..4a638f3 100644
--- a/tests/src/Unit/Drush/TfaTokenManagementTest.php
+++ b/tests/src/Unit/Drush/TfaTokenManagementTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Logger\LoggerChannelInterface;
 use Drupal\Core\Mail\MailManagerInterface;
@@ -23,6 +25,7 @@ require_once __DIR__ . '/../../../../../../../../vendor/drush/drush/includes/out
  *
  * @group tfa
  */
+#[Group('tfa')]
 final class TfaTokenManagementTest extends UnitTestCase {
 
   /**
@@ -100,6 +103,7 @@ final class TfaTokenManagementTest extends UnitTestCase {
    *
    * @dataProvider providerTestResetUserTfaData
    */
+  #[DataProvider('providerTestResetUserTfaData')]
   public function testResetUserTfaData(callable $setup, array $options): void {
     $setup($this);
     $service = $this->getFixture();
diff --git a/tests/src/Unit/EventSubscriber/TfaOneTimeLoginEventSubscriberTest.php b/tests/src/Unit/EventSubscriber/TfaOneTimeLoginEventSubscriberTest.php
index 3aea3ad..0b3a754 100644
--- a/tests/src/Unit/EventSubscriber/TfaOneTimeLoginEventSubscriberTest.php
+++ b/tests/src/Unit/EventSubscriber/TfaOneTimeLoginEventSubscriberTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\EventSubscriber;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Config\ConfigFactoryInterface;
@@ -46,6 +48,7 @@ require_once __DIR__ . '/../../../../../../../core/modules/user/user.module';
  *
  * @group tfa
  */
+#[Group('tfa')]
 final class TfaOneTimeLoginEventSubscriberTest extends UnitTestCase {
   use TfaLoginTrait;
 
@@ -426,6 +429,7 @@ final class TfaOneTimeLoginEventSubscriberTest extends UnitTestCase {
    *
    * @dataProvider providerTestLinkValidationLogic
    */
+  #[DataProvider('providerTestLinkValidationLogic')]
   public function testLinkValidationLogic(bool $expect_redirect_to_user_pass_page, int $request_time, int $last_login_time, string $link_generated_time, string $link_hash, string $expected_messenger_error): void {
     $this->timeMock = $this->createMock(TimeInterface::class);
     $this->timeMock->method('getRequestTime')->willReturn($request_time);
diff --git a/tests/src/Unit/EventSubscriber/TfaUserSetSubscriberTest.php b/tests/src/Unit/EventSubscriber/TfaUserSetSubscriberTest.php
index d9ec434..e291f14 100644
--- a/tests/src/Unit/EventSubscriber/TfaUserSetSubscriberTest.php
+++ b/tests/src/Unit/EventSubscriber/TfaUserSetSubscriberTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\EventSubscriber;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Logger\LoggerChannelInterface;
@@ -33,6 +35,7 @@ use Symfony\Component\HttpKernel\KernelEvents;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaUserSetSubscriberTest extends UnitTestCase {
 
   /**
@@ -175,6 +178,7 @@ class TfaUserSetSubscriberTest extends UnitTestCase {
    *
    * @dataProvider providerRejectUserIfTfaBypassed
    */
+  #[DataProvider('providerRejectUserIfTfaBypassed')]
   public function testRejectUserIfTfaBypassed(
     \stdClass|false $bypass_result,
     \stdClass|false $switcher_result,
@@ -467,6 +471,7 @@ class TfaUserSetSubscriberTest extends UnitTestCase {
    *
    * @dataProvider providerTestOnException
    */
+  #[DataProvider('providerTestOnException')]
   public function testOnException(\Throwable $exception, callable $test): void {
     $fixture = $this->getFixture();
     $kernel_mock = $this->createMock(HttpKernelInterface::class);
diff --git a/tests/src/Unit/Form/TfaEntryFormTest.php b/tests/src/Unit/Form/TfaEntryFormTest.php
index c518b6a..0db44dc 100644
--- a/tests/src/Unit/Form/TfaEntryFormTest.php
+++ b/tests/src/Unit/Form/TfaEntryFormTest.php
@@ -32,6 +32,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
  *
  * @group tfa
  */
+#[\PHPUnit\Framework\Attributes\Group('tfa')]
 class TfaEntryFormTest extends UnitTestCase {
 
   /**
@@ -140,6 +141,7 @@ class TfaEntryFormTest extends UnitTestCase {
    *
    * @dataProvider providerSetCompleteFlag
    */
+  #[\PHPUnit\Framework\Attributes\DataProvider('providerSetCompleteFlag')]
   public function testSetCompleteFlag(int $count, bool $validate_return): void {
     $this->floodControlMock->method('isAllowed')->willReturn(TRUE);
     $this->tfaValidationPluginMock->method('ready')->willReturn(TRUE);
diff --git a/tests/src/Unit/Form/TfaLoginFormHelperTest.php b/tests/src/Unit/Form/TfaLoginFormHelperTest.php
index 022dfae..4951f48 100644
--- a/tests/src/Unit/Form/TfaLoginFormHelperTest.php
+++ b/tests/src/Unit/Form/TfaLoginFormHelperTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\Form;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
@@ -34,6 +36,7 @@ use Symfony\Component\HttpFoundation\Session\SessionInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaLoginFormHelperTest extends UnitTestCase {
 
   /**
@@ -128,6 +131,7 @@ class TfaLoginFormHelperTest extends UnitTestCase {
    *
    * @dataProvider providerTfaLoginContextScenarios
    */
+  #[DataProvider('providerTfaLoginContextScenarios')]
   public function testSetCompleteFlag(bool $tfa_entry_required, array $login_context_values): void {
 
     $expected_memcache_calls = $this->once();
@@ -172,6 +176,7 @@ class TfaLoginFormHelperTest extends UnitTestCase {
    *
    * @dataProvider providerTfaLoginContextScenarios
    */
+  #[DataProvider('providerTfaLoginContextScenarios')]
   public function testSessionMigrated(bool $tfa_entry_required, array $login_context_values): void {
 
     $expected_call_count = $this->never();
diff --git a/tests/src/Unit/Plugin/Tfa/TfaHotpTest.php b/tests/src/Unit/Plugin/Tfa/TfaHotpTest.php
index 24dc65f..b41248e 100644
--- a/tests/src/Unit/Plugin/Tfa/TfaHotpTest.php
+++ b/tests/src/Unit/Plugin/Tfa/TfaHotpTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\Plugin\Tfa;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Form\FormState;
@@ -28,6 +30,7 @@ use PHPUnit\Framework\MockObject\MockObject;
  *
  * @group tfa
  */
+#[Group('tfa')]
 final class TfaHotpTest extends UnitTestCase {
 
   /**
@@ -203,6 +206,7 @@ final class TfaHotpTest extends UnitTestCase {
    *
    * @dataProvider providerValidate()
    */
+  #[DataProvider('providerValidate()')]
   public function testValidateRequest(bool $expected_result, string $submitted_code, ?callable $setup = NULL, int $expected_counter = self::NEXT_COUNTER + 1): void {
     $this->lockMock->expects($this->exactly(2))->method('acquire')->with('tfa_validation_hotp_3', $this->anything())->willReturnOnConsecutiveCalls(FALSE, TRUE);
     $this->lockMock->expects($this->once())->method('release')->with('tfa_validation_hotp_3');
@@ -232,6 +236,7 @@ final class TfaHotpTest extends UnitTestCase {
    *
    * @dataProvider providerValidate()
    */
+  #[DataProvider('providerValidate()')]
   public function testValidateForm(bool $expected_result, string $submitted_code, ?callable $setup = NULL, int $expected_counter = self::NEXT_COUNTER + 1): void {
     $this->lockMock->expects($this->exactly(2))->method('acquire')->with('tfa_validation_hotp_3', $this->anything())->willReturnOnConsecutiveCalls(FALSE, TRUE);
     $this->lockMock->expects($this->once())->method('release')->with('tfa_validation_hotp_3');
diff --git a/tests/src/Unit/Plugin/Tfa/TfaRecoveryCodeTest.php b/tests/src/Unit/Plugin/Tfa/TfaRecoveryCodeTest.php
index cf00e16..d0fcddc 100644
--- a/tests/src/Unit/Plugin/Tfa/TfaRecoveryCodeTest.php
+++ b/tests/src/Unit/Plugin/Tfa/TfaRecoveryCodeTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\tfa\Unit\Plugin\Tfa;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Form\FormState;
 use Drupal\Core\Lock\LockBackendInterface;
@@ -18,6 +19,7 @@ use Drupal\user\UserDataInterface;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaRecoveryCodeTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/Plugin/Tfa/TfaTotpTest.php b/tests/src/Unit/Plugin/Tfa/TfaTotpTest.php
index 2be7c83..0114757 100644
--- a/tests/src/Unit/Plugin/Tfa/TfaTotpTest.php
+++ b/tests/src/Unit/Plugin/Tfa/TfaTotpTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit\Plugin\Tfa;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Form\FormState;
@@ -28,6 +30,7 @@ use PHPUnit\Framework\MockObject\MockObject;
  *
  * @group tfa
  */
+#[Group('tfa')]
 final class TfaTotpTest extends UnitTestCase {
 
   /**
@@ -209,6 +212,7 @@ final class TfaTotpTest extends UnitTestCase {
    *
    * @dataProvider providerValidate()
    */
+  #[DataProvider('providerValidate()')]
   public function testValidateRequest(bool $expected_result, string $submitted_code, ?callable $setup = NULL, int $expected_time_window = 53333333): void {
     $this->lockMock->expects($this->exactly(2))->method('acquire')->with('tfa_validation_totp_3', $this->anything())->willReturnOnConsecutiveCalls(FALSE, TRUE);
     $this->lockMock->expects($this->once())->method('release')->with('tfa_validation_totp_3');
@@ -238,6 +242,7 @@ final class TfaTotpTest extends UnitTestCase {
    *
    * @dataProvider providerValidate()
    */
+  #[DataProvider('providerValidate()')]
   public function testValidateForm(bool $expected_result, string $submitted_code, ?callable $setup = NULL, int $expected_time_window = 53333333): void {
     $this->lockMock->expects($this->exactly(2))->method('acquire')->with('tfa_validation_totp_3', $this->anything())->willReturnOnConsecutiveCalls(FALSE, TRUE);
     $this->lockMock->expects($this->once())->method('release')->with('tfa_validation_totp_3');
diff --git a/tests/src/Unit/PostUpdateHooks/UserData0001RemoveSmsTest.php b/tests/src/Unit/PostUpdateHooks/UserData0001RemoveSmsTest.php
index cf4f894..95bdc81 100644
--- a/tests/src/Unit/PostUpdateHooks/UserData0001RemoveSmsTest.php
+++ b/tests/src/Unit/PostUpdateHooks/UserData0001RemoveSmsTest.php
@@ -2,6 +2,7 @@
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Entity\EntityTypeManagerInterface;
 use Drupal\Core\Entity\Query\QueryInterface;
@@ -19,6 +20,7 @@ require __DIR__ . '/../../../../tfa_post_update.php';
  *
  * @group tfa
  */
+#[Group('tfa')]
 class UserData0001RemoveSmsTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/TfaAccountSwitcherTest.php b/tests/src/Unit/TfaAccountSwitcherTest.php
index 81f6395..50f41ae 100644
--- a/tests/src/Unit/TfaAccountSwitcherTest.php
+++ b/tests/src/Unit/TfaAccountSwitcherTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\Cache\Cache;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\Session\AccountInterface;
@@ -20,6 +21,7 @@ use PHPUnit\Framework\MockObject\MockObject;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaAccountSwitcherTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/TfaContextTest.php b/tests/src/Unit/TfaContextTest.php
index a4bcbce..c93dc48 100644
--- a/tests/src/Unit/TfaContextTest.php
+++ b/tests/src/Unit/TfaContextTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
@@ -22,6 +24,7 @@ use PHPUnit\Framework\MockObject\MockObject;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaContextTest extends UnitTestCase {
 
   /**
@@ -390,6 +393,7 @@ class TfaContextTest extends UnitTestCase {
    *
    * @dataProvider providerCanLoginWithoutTfa
    */
+  #[DataProvider('providerCanLoginWithoutTfa')]
   public function testCanLoginWithoutTfa(bool $expected_result, bool $has_permission_setup_own_tfa, int $skips_used, string $message_constraint, ?callable $setup = NULL): void {
 
     if ($setup !== NULL) {
diff --git a/tests/src/Unit/TfaModuleTest.php b/tests/src/Unit/TfaModuleTest.php
index 3da6f8e..1ea9a9f 100644
--- a/tests/src/Unit/TfaModuleTest.php
+++ b/tests/src/Unit/TfaModuleTest.php
@@ -4,6 +4,8 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Core\Cache\CacheBackendInterface;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Core\Session\AccountInterface;
@@ -18,6 +20,7 @@ require_once __DIR__ . '/../../../tfa.module';
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaModuleTest extends UnitTestCase {
 
   /**
@@ -27,6 +30,7 @@ class TfaModuleTest extends UnitTestCase {
    *
    * @dataProvider providerTfaUserLoginSetTfaComplete
    */
+  #[DataProvider('providerTfaUserLoginSetTfaComplete')]
   public function testTfaUserLoginSetTfaComplete(
     \stdClass|FALSE $memory_cache_return,
     int $set_call_count,
diff --git a/tests/src/Unit/TfaServiceProviderTest.php b/tests/src/Unit/TfaServiceProviderTest.php
index ca8bfc2..94546ee 100644
--- a/tests/src/Unit/TfaServiceProviderTest.php
+++ b/tests/src/Unit/TfaServiceProviderTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
 use Drupal\Core\DependencyInjection\ContainerBuilder;
 use Drupal\Tests\UnitTestCase;
 use Drupal\tfa\Compiler\TfaAuthDecoratorCompiler;
@@ -16,6 +17,7 @@ use Drupal\tfa\TfaServiceProvider;
  *
  * @group tfa
  */
+#[Group('tfa')]
 class TfaServiceProviderTest extends UnitTestCase {
 
   /**
diff --git a/tests/src/Unit/TfaUserAuthTest.php b/tests/src/Unit/TfaUserAuthTest.php
index e7c395e..6fbd036 100644
--- a/tests/src/Unit/TfaUserAuthTest.php
+++ b/tests/src/Unit/TfaUserAuthTest.php
@@ -2,6 +2,8 @@
 
 namespace Drupal\Tests\tfa\Unit;
 
+use PHPUnit\Framework\Attributes\Group;
+use PHPUnit\Framework\Attributes\DataProvider;
 use Drupal\Component\Datetime\TimeInterface;
 use Drupal\Component\Plugin\Exception\PluginException;
 use Drupal\Core\Cache\Cache;
@@ -30,6 +32,8 @@ use PHPUnit\Framework\MockObject\Rule\InvokedCount;
  *
  * cSpell:ignore abcxyz
  */
+#[Group('tfa
+cSpell:ignore abcxyz')]
 class TfaUserAuthTest extends UnitTestCase {
 
   /**
@@ -284,6 +288,7 @@ class TfaUserAuthTest extends UnitTestCase {
    *
    * @dataProvider providerTfaDisabled
    */
+  #[DataProvider('providerTfaDisabled')]
   public function testTfaDisabled(int|false $inner_result): void {
     $this->loginContextMock->method('isTfaDisabled')->willReturn(TRUE);
     $this->innerUserAuthMock->expects($this->atMost(1))->method('authenticate')->willReturn($inner_result);
@@ -327,6 +332,7 @@ class TfaUserAuthTest extends UnitTestCase {
    *
    * @dataProvider providerLoginSkipped
    */
+  #[DataProvider('providerLoginSkipped')]
   public function testLoginSkipped(int|false $expected_result, bool $can_login_without_tfa, int|false $inner_result): void {
 
     $this->loginContextMock->method('isTfaDisabled')->willReturn(FALSE);
@@ -383,6 +389,7 @@ class TfaUserAuthTest extends UnitTestCase {
    *
    * @dataProvider providerLoginPluginAllows
    */
+  #[DataProvider('providerLoginPluginAllows')]
   public function testLoginPluginAllows(int|false $expected_result, int|false $inner_result): void {
 
     $this->loginContextMock->method('isTfaDisabled')->willReturn(FALSE);
@@ -429,6 +436,7 @@ class TfaUserAuthTest extends UnitTestCase {
    *
    * @dataProvider providerValidatedPluginAuthentication
    */
+  #[DataProvider('providerValidatedPluginAuthentication')]
   public function testValidatedPluginAuthentication(int|false $expected_result, string $default_plugin, int|string|false $inner_result): void {
 
     $this->tfaSettings = [
@@ -539,6 +547,7 @@ class TfaUserAuthTest extends UnitTestCase {
    *
    * @dataProvider providerBypass
    */
+  #[DataProvider('providerBypass')]
   public function testBypass(int|false $expected_result, InvokedCount $validate_request_constraint, InvokedCount $set_force_logout_constraint, array $callback_match, string $expected_password, int|false $inner_result, \stdClass|null $bypass_result): void {
 
     $this->loginContextMock->method('isTfaDisabled')->willReturn(FALSE);
diff --git a/tfa.install b/tfa.install
index 9d1e3d8..7a4f854 100644
--- a/tfa.install
+++ b/tfa.install
@@ -5,6 +5,8 @@
  * Installation related functions for TFA module.
  */
 
+use Drupal\Component\Utility\DeprecationHelper;
+use Drupal\Core\Extension\Requirement\RequirementSeverity;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\user\Entity\Role;
 
@@ -16,11 +18,11 @@ function tfa_requirements(string $phase): array {
   if ($phase == 'runtime') {
     if (!extension_loaded('openssl')) {
       if (extension_loaded('mcrypt')) {
-        $severity = REQUIREMENT_WARNING;
+        $severity = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::Warning, fn() => REQUIREMENT_WARNING);
         $description = t('The TFA module recommends the PHP OpenSSL extension to be installed on the web server.');
       }
       else {
-        $severity = REQUIREMENT_ERROR;
+        $severity = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::Error, fn() => REQUIREMENT_ERROR);
         $description = t('The TFA module requires either the PHP OpenSSL or Mcrypt extensions to be installed on the web server.');
       }
 
@@ -36,12 +38,12 @@ function tfa_requirements(string $phase): array {
       'title' => t('Two-factor authentication'),
     ];
     if (class_exists('\OTPHP\OTP')) {
-      $requirements['tfa_libraries']['severity'] = REQUIREMENT_OK;
+      $requirements['tfa_libraries']['severity'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::OK, fn() => REQUIREMENT_OK);
       $requirements['tfa_libraries']['value'] = t('Third-party libraries');
       $requirements['tfa_libraries']['description'] = t('One Time Passwords (spomky-labs/otphp) is installed');
     }
     else {
-      $requirements['tfa_libraries']['severity'] = REQUIREMENT_ERROR;
+      $requirements['tfa_libraries']['severity'] = DeprecationHelper::backwardsCompatibleCall(\Drupal::VERSION, '11.2.0', fn() => RequirementSeverity::Error, fn() => REQUIREMENT_ERROR);
       $requirements['tfa_libraries']['value'] = t('Third-party libraries');
       $requirements['tfa_libraries']['description'] = t("Please install the 'spomky-labs/otphp' library via composer. See the module README for instructions.");
     }
diff --git a/tfa.module b/tfa.module
index 2a4ae8d..7d5d569 100644
--- a/tfa.module
+++ b/tfa.module
@@ -5,116 +5,58 @@
  * Contains tfa.module.
  */
 
-use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\tfa\Hook\TfaHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
 use Drupal\Core\Routing\RouteMatchInterface;
-use Drupal\Core\Url;
 use Drupal\user\UserInterface;
 
 /**
  * Implements hook_help().
  */
+#[LegacyHook]
 function tfa_help(string $route_name, RouteMatchInterface $route_match): string {
-  $output = '';
-  switch ($route_name) {
-    // Main module help for the tfa module.
-    case 'help.page.tfa':
-      $output .= '<h3>' . \Drupal::translation()->translate('About') . '</h3>';
-      $output .= '<p>' . \Drupal::translation()->translate('Pluggable provider of second factor authentication for Drupal. For more information, see the online documentation for the <a href=":tfa">Two-factor Authentication</a> module.',
-        [':tfa' => 'https://www.drupal.org/project/tfa']
-      ) . '</p>';
-  }
-  return $output;
+  return \Drupal::service(TfaHooks::class)->help($route_name, $route_match);
 }
 
 /**
  * Implements hook_entity_operation().
  */
+#[LegacyHook]
 function tfa_entity_operation(EntityInterface $entity): array {
-  $operations = [];
-
-  if ($entity instanceof UserInterface) {
-    $url = Url::fromRoute('tfa.overview', ['user' => $entity->id()]);
-    if ($url->access() === TRUE) {
-      $operations['tfa'] = [
-        'title' => t('TFA'),
-        'url' => $url,
-        'weight' => 50,
-      ];
-    }
-  }
-
-  return $operations;
+  return \Drupal::service(TfaHooks::class)->entityOperation($entity);
 }
 
 /**
  * Implements hook_mail().
  */
+#[LegacyHook]
 function tfa_mail(string $key, array &$message, array $params): void {
-  $token_service = \Drupal::token();
-  $language_manager = \Drupal::languageManager();
-  $variables = ['user' => $params['account']];
-
-  $language = $language_manager->getLanguage($params['account']->getPreferredLangcode());
-  $original_language = $language_manager->getConfigOverrideLanguage();
-  $language_manager->setConfigOverrideLanguage($language);
-  $tfa_config = \Drupal::config('tfa.settings');
-
-  $token_options = [
-    'langcode' => $message['langcode'],
-    'clear' => TRUE,
-  ];
-
-  // Configuration mapping key matches the hook_mail() $key.
-  $subject = $tfa_config->get("mail.{$key}.subject");
-  $subject = $token_service->replace($subject, $variables, $token_options);
-  $message['subject'] = PlainTextOutput::renderFromHtml($subject);
-
-  $body = $tfa_config->get("mail.{$key}.body");
-  $message['body'][] = $token_service->replace($body, $variables, $token_options);
-
-  $language_manager->setConfigOverrideLanguage($original_language);
+  \Drupal::service(TfaHooks::class)->mail($key, $message, $params);
 }
 
 /**
  * Implements hook_user_login().
  */
+#[LegacyHook]
 function tfa_user_login(UserInterface $account): void {
-  /** @var \Drupal\Core\Cache\CacheBackendInterface $memory_cache */
-  $memory_cache = \Drupal::service('cache.tfa_memcache');
-
-  /** @var FALSE|object{'data': mixed} $tfa_complete_this_request */
-  $tfa_complete_this_request = $memory_cache->get('tfa_complete');
-  if ($tfa_complete_this_request !== FALSE) {
-    $user_auth_as_id = $tfa_complete_this_request->data;
-    if (is_int($user_auth_as_id) && $user_auth_as_id === (int) $account->id()) {
-      /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
-      $session = \Drupal::service('session');
-      $session->set('tfa_complete', (int) $account->id());
-    }
-  }
-
+  \Drupal::service(TfaHooks::class)->userLogin($account);
 }
 
 /**
  * Implements hook_user_logout().
  */
+#[LegacyHook]
 function tfa_user_logout(AccountInterface $account): void {
-  /** @var \Symfony\Component\HttpFoundation\Session\SessionInterface $session */
-  $session = \Drupal::service('session');
-  $session->remove('tfa_complete');
+  \Drupal::service(TfaHooks::class)->userLogout($account);
 }
 
 /**
  * Implements hook_form_alter().
  */
+#[LegacyHook]
 function tfa_form_alter(array &$form, FormStateInterface &$form_state, string $form_id): void {
-  switch ($form_id) {
-    case 'user_login_form':
-    case 'user_login_block':
-      \Drupal::service('tfa.login_form_helper')->alterLoginForm($form);
-      break;
-  }
+  \Drupal::service(TfaHooks::class)->formAlter($form, $form_state, $form_id);
 }
diff --git a/tfa.services.yml b/tfa.services.yml
index 15aa406..41b5c39 100644
--- a/tfa.services.yml
+++ b/tfa.services.yml
@@ -72,3 +72,11 @@ services:
       - '@private_key'
       - '@session'
       - '@redirect.destination'
+
+  Drupal\tfa\Hook\TfaHooks:
+    class: Drupal\tfa\Hook\TfaHooks
+    autowire: true
+
+  Drupal\tfa\Hook\TfaViewsHooks:
+    class: Drupal\tfa\Hook\TfaViewsHooks
+    autowire: true
diff --git a/tfa.views.inc b/tfa.views.inc
index 42bd6e2..d2426c3 100644
--- a/tfa.views.inc
+++ b/tfa.views.inc
@@ -1,5 +1,12 @@
 <?php
 
+/**
+ * @file
+ */
+
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\tfa\Hook\TfaViewsHooks;
+
 /**
  * @file
  * TFA views configuration.
@@ -8,14 +15,7 @@
 /**
  * Implements hook_views_data_alter().
  */
+#[LegacyHook]
 function tfa_views_data_alter(array &$data): void {
-  $data['users']['tfa_enabled_field'] = [
-    'title' => t('TFA enabled'),
-    'real field' => 'uid',
-    'field' => [
-      'title' => t('TFA enabled'),
-      'help' => t('Whether the user has enabled two-factor authentication.'),
-      'id' => 'tfa_enabled_field',
-    ],
-  ];
+  \Drupal::service(TfaViewsHooks::class)->viewsDataAlter($data);
 }
