diff --git a/magic_code.module b/magic_code.module
index e1130b9..63372cb 100644
--- a/magic_code.module
+++ b/magic_code.module
@@ -7,30 +7,17 @@
 
 declare(strict_types=1);
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\magic_code\Hook\MagicCodeHooks;
 use Drupal\Core\Entity\EntityInterface;
 use Drupal\Core\Form\FormStateInterface;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
-use Drupal\user\UserInterface;
 
 /**
  * Implements hook_cron().
  */
+#[LegacyHook]
 function magic_code_cron() {
-  /** @var \Drupal\magic_code\MagicCodeCollector $collector */
-  $collector = \Drupal::service('magic_code.collector');
-  $logger = \Drupal::service('logger.channel.magic_code');
-
-  $limit = 0;
-
-  // Delete a batch of expired codes.
-  if (!empty($expiredCodes = $collector->collectExpired($limit))) {
-    $count = count($expiredCodes);
-    $collector->deleteMultipleMagicCodes($expiredCodes);
-
-    $logger->notice('Deleted @count expired magic codes in cron.', [
-      '@count' => $count,
-    ]);
-  }
+  \Drupal::service(MagicCodeHooks::class)->cron();
 }
 
 /**
@@ -38,50 +25,17 @@ function magic_code_cron() {
  *
  * Revokes all magic codes for updated user.
  */
+#[LegacyHook]
 function magic_code_entity_update(EntityInterface $entity) {
-  if (!$entity instanceof UserInterface) {
-    return;
-  }
-
-  /** @var \Drupal\magic_code\MagicCodeCollector $collector */
-  $collector = \Drupal::service('magic_code.collector');
-  $logger = \Drupal::service('logger.channel.magic_code');
-
-  if (!empty($codes = $collector->collectForAccount($entity))) {
-    $count = count($codes);
-
-    foreach ($codes as $code) {
-      $code->revoke()->save();
-    }
-
-    $logger->notice('Revoked @count magic codes for user @id due to user entity update.', [
-      '@count' => $count,
-      '@id' => $entity->id(),
-    ]);
-  }
+  \Drupal::service(MagicCodeHooks::class)->entityUpdate($entity);
 }
 
 /**
  * Implements hook_mail_alter().
  */
+#[LegacyHook]
 function magic_code_mail_alter(&$message) {
-  $replaceInEmail = FALSE;
-
-  $context = [
-    'message' => $message,
-  ];
-
-  // Alow user emails by default.
-  if (strpos($message['id'], 'user_') === 0) {
-    $replaceInEmail = TRUE;
-  }
-
-  // Let modules alter the decision.
-  \Drupal::moduleHandler()->alter('magic_code_replace_token_in_email', $replaceInEmail, $context);
-
-  if ($replaceInEmail) {
-    _magic_code_replace_mail_tokens($message);
-  }
+  \Drupal::service(MagicCodeHooks::class)->mailAlter($message);
 }
 
 /**
@@ -195,91 +149,9 @@ function _magic_code_get_available_tokens() {
  *
  * Adds flood config options to config form of flood_control module.
  */
+#[LegacyHook]
 function magic_code_form_flood_control_settings_form_alter(&$form) {
-  $floodConfigCreation = \Drupal::configFactory()->get('magic_code.settings')->get('flood_creation');
-  $floodConfigVerification = \Drupal::configFactory()->get('magic_code.settings')->get('flood_verification');
-
-  // Magic Code Creation.
-  $form['magic_code_creation'] = [
-    '#type' => 'fieldset',
-    '#title' => new TranslatableMarkup('Magic Code - Creation'),
-  ];
-
-  $form['magic_code_creation']['info'] = [
-    '#markup' => new TranslatableMarkup('Applies to whenever a magic code is being created. This is to prevent an attacker from creating loads of magic codes.'),
-  ];
-
-  $form['magic_code_creation']['magic_code_creation_ip_limit'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('IP limit'),
-    '#options' => $form['login']['ip_limit']['#options'],
-    '#default_value' => $floodConfigCreation['ip_limit'],
-    '#description' => new TranslatableMarkup('The allowed number of magic code creations from one IP address within the allowed time window.'),
-  ];
-
-  $form['magic_code_creation']['magic_code_creation_ip_window'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('IP time window'),
-    '#options' => $form['login']['ip_window']['#options'],
-    '#default_value' => $floodConfigCreation['ip_window'],
-    '#description' => new TranslatableMarkup('The allowed time window for magic code creations by IP.'),
-  ];
-  $form['magic_code_creation']['magic_code_creation_user_limit'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('User limit'),
-    '#options' => $form['login']['user_limit']['#options'],
-    '#default_value' => $floodConfigCreation['user_limit'],
-    '#description' => new TranslatableMarkup('The allowed number of magic code creations for a given user within the allowed time window.'),
-  ];
-  $form['magic_code_creation']['magic_code_creation_user_window'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('User time window'),
-    '#options' => $form['login']['user_window']['#options'],
-    '#default_value' => $floodConfigCreation['user_window'],
-    '#description' => new TranslatableMarkup('The allowed time window for magic code creations by user.'),
-  ];
-
-  // Magic Code Verification.
-  $form['magic_code_verification'] = [
-    '#type' => 'fieldset',
-    '#title' => new TranslatableMarkup('Magic Code - Verification'),
-  ];
-
-  $form['magic_code_verification']['info'] = [
-    '#markup' => new TranslatableMarkup('Applies to whenever a magic code is being verified.'),
-  ];
-
-  $form['magic_code_verification']['magic_code_verification_ip_limit'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('IP limit'),
-    '#options' => $form['login']['ip_limit']['#options'],
-    '#default_value' => $floodConfigVerification['ip_limit'],
-    '#description' => new TranslatableMarkup('The allowed number of magic code verifications from one IP address within the allowed time window.'),
-  ];
-
-  $form['magic_code_verification']['magic_code_verification_ip_window'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('IP time window'),
-    '#options' => $form['login']['ip_window']['#options'],
-    '#default_value' => $floodConfigVerification['ip_window'],
-    '#description' => new TranslatableMarkup('The allowed time window for magic code verifications by IP.'),
-  ];
-  $form['magic_code_verification']['magic_code_verification_user_limit'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('User limit'),
-    '#options' => $form['login']['user_limit']['#options'],
-    '#default_value' => $floodConfigVerification['user_limit'],
-    '#description' => new TranslatableMarkup('The allowed number of magic code verifications for a given user within the allowed time window.'),
-  ];
-  $form['magic_code_verification']['magic_code_verification_user_window'] = [
-    '#type' => 'select',
-    '#title' => new TranslatableMarkup('User time window'),
-    '#options' => $form['login']['user_window']['#options'],
-    '#default_value' => $floodConfigVerification['user_window'],
-    '#description' => new TranslatableMarkup('The allowed time window for magic code verifications by user.'),
-  ];
-
-  $form['#submit'][] = '_magic_code_form_flood_control_settings_submit';
+  \Drupal::service(MagicCodeHooks::class)->formFloodControlSettingsFormAlter($form);
 }
 
 /**
diff --git a/magic_code.services.yml b/magic_code.services.yml
index e869857..25c90af 100644
--- a/magic_code.services.yml
+++ b/magic_code.services.yml
@@ -21,3 +21,11 @@ services:
   Drupal\magic_code\Flood\MagicCodeFloodControl: ~
   Drupal\magic_code\Flood\MagicCodeFloodControlInterface: '@Drupal\magic_code\Flood\MagicCodeFloodControl'
   magic_code.flood: '@Drupal\magic_code\Flood\MagicCodeFloodControl'
+
+  Drupal\magic_code\Hook\MagicCodeHooks:
+    class: Drupal\magic_code\Hook\MagicCodeHooks
+    autowire: true
+
+  Drupal\magic_code\Hook\MagicCodeTokensHooks:
+    class: Drupal\magic_code\Hook\MagicCodeTokensHooks
+    autowire: true
diff --git a/magic_code.tokens.inc b/magic_code.tokens.inc
index f73846f..09db52a 100644
--- a/magic_code.tokens.inc
+++ b/magic_code.tokens.inc
@@ -7,59 +7,22 @@
 
 declare(strict_types=1);
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\magic_code\Hook\MagicCodeTokensHooks;
 use Drupal\Core\Render\BubbleableMetadata;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
 
 /**
  * Implements hook_token_info().
  */
+#[LegacyHook]
 function magic_code_token_info() {
-  $operations = _magic_code_get_available_tokens();
-  $tokens = [];
-
-  foreach ($operations as $operation) {
-    $key = 'magic-code-' . $operation;
-
-    $tokens[$key] = [
-      'name' => new TranslatableMarkup('Magic Code - @operation', [
-        '@operation' => $operation,
-      ]),
-      'description' => new TranslatableMarkup('A magic code with the operation "@operation"', [
-        '@operation' => $operation,
-      ]),
-      'restricted' => TRUE,
-    ];
-  }
-
-  return [
-    'tokens' => [
-      'user' => $tokens,
-    ],
-  ];
+  return \Drupal::service(MagicCodeTokensHooks::class)->tokenInfo();
 }
 
 /**
  * Implements hook_tokens().
  */
+#[LegacyHook]
 function magic_code_tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
-  $replacements = [];
-
-  if ($type == 'user' && !empty($data['user'])) {
-    $operations = _magic_code_get_available_tokens();
-
-    foreach ($tokens as $name => $original) {
-      // Safety check.
-      if (!str_contains($name, 'magic-code-')) {
-        continue;
-      }
-
-      $operation = str_replace('magic-code-', '', $name);
-
-      if (in_array($operation, $operations)) {
-        $replacements[$original] = sprintf('[user:magic-code-%s_mail-only]', $operation);
-      }
-    }
-  }
-
-  return $replacements;
+  return \Drupal::service(MagicCodeTokensHooks::class)->tokens($type, $tokens, $data, $options, $bubbleable_metadata);
 }
diff --git a/modules/magic_code_email_login/magic_code_email_login.module b/modules/magic_code_email_login/magic_code_email_login.module
index b53f045..28c7d5e 100644
--- a/modules/magic_code_email_login/magic_code_email_login.module
+++ b/modules/magic_code_email_login/magic_code_email_login.module
@@ -7,10 +7,10 @@
 
 declare(strict_types=1);
 
-use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\magic_code_email_login\Hook\MagicCodeEmailLoginHooks;
 use Drupal\Core\Form\FormStateInterface;
 use Drupal\Core\Session\AccountInterface;
-use Drupal\Core\StringTranslation\TranslatableMarkup;
 use Drupal\user\UserInterface;
 
 /**
@@ -18,19 +18,9 @@ use Drupal\user\UserInterface;
  *
  * Clears the user based flood on user login.
  */
+#[LegacyHook]
 function magic_code_email_login_user_login(UserInterface $user) {
-  $userFloodConfig = \Drupal::config('user.flood');
-  $request = \Drupal::request();
-
-  if ($userFloodConfig->get('uid_only')) {
-    $identifier = $user->id();
-  }
-  else {
-    $identifier = $user->id() . '-' . $request->getClientIP();
-  }
-
-  // Clear user.
-  \Drupal::flood()->clear('magic_code_email_login.failed_user', $identifier);
+  \Drupal::service(MagicCodeEmailLoginHooks::class)->userLogin($user);
 }
 
 /**
@@ -50,31 +40,9 @@ function magic_code_email_login_config_translation_info(&$definitions) {
  *
  * Make the magic code email configurable in the admin account settings.
  */
+#[LegacyHook]
 function magic_code_email_login_form_user_admin_settings_alter(&$form, FormStateInterface $form_state, $form_id) {
-  $mailConfig = \Drupal::config('magic_code_email_login.mail');
-
-  $form['magic_code_email_login'] = [
-    '#type' => 'details',
-    '#title' => new TranslatableMarkup('Magic Code Login'),
-    '#collapsible' => TRUE,
-    '#collapsed' => TRUE,
-    '#group' => 'email',
-    '#weight' => 12,
-  ];
-  $form['magic_code_email_login']['magic_code_email_login_subject'] = [
-    '#type' => 'textfield',
-    '#title' => new TranslatableMarkup('Subject'),
-    '#default_value' => $mailConfig->get('login.subject'),
-    '#maxlength' => 180,
-  ];
-  $form['magic_code_email_login']['magic_code_email_login_body'] = [
-    '#type' => 'textarea',
-    '#title' => new TranslatableMarkup('Body'),
-    '#default_value' => $mailConfig->get('login.body'),
-    '#rows' => 12,
-  ];
-
-  $form['#submit'][] = '_magic_code_email_login_form_user_admin_settings_submit';
+  \Drupal::service(MagicCodeEmailLoginHooks::class)->formUserAdminSettingsAlter($form, $form_state, $form_id);
 }
 
 /**
@@ -92,36 +60,17 @@ function _magic_code_email_login_form_user_admin_settings_submit(array &$form, F
 /**
  * Implements hook_mail().
  */
+#[LegacyHook]
 function magic_code_email_login_mail($key, &$message, $params) {
-  $token_service = \Drupal::token();
-  $language_manager = \Drupal::languageManager();
-  $langcode = $message['langcode'];
-  $variables = ['user' => $params['account']];
-
-  $language = $language_manager->getLanguage($langcode);
-  $original_language = $language_manager->getConfigOverrideLanguage();
-  $language_manager->setConfigOverrideLanguage($language);
-  $mail_config = \Drupal::config('magic_code_email_login.mail');
-
-  $token_options = [
-    'langcode' => $langcode,
-    'callback' => 'user_mail_tokens',
-    'clear' => TRUE,
-  ];
-  $message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($mail_config->get($key . '.subject'), $variables, $token_options));
-  $message['body'][] = $token_service->replace($mail_config->get($key . '.body'), $variables, $token_options);
-
-  $language_manager->setConfigOverrideLanguage($original_language);
+  \Drupal::service(MagicCodeEmailLoginHooks::class)->mail($key, $message, $params);
 }
 
 /**
  * Implements hook_magic_code_replace_token_in_email_alter().
  */
+#[LegacyHook]
 function magic_code_email_login_magic_code_replace_token_in_email_alter(&$replaceInEmail, &$context) {
-  // Replace token in this module's login email.
-  if ($context['message']['id'] === 'magic_code_email_login_login') {
-    $replaceInEmail = TRUE;
-  }
+  \Drupal::service(MagicCodeEmailLoginHooks::class)->magicCodeReplaceTokenInEmailAlter($replaceInEmail, $context);
 }
 
 /**
diff --git a/modules/magic_code_email_login/magic_code_email_login.services.yml b/modules/magic_code_email_login/magic_code_email_login.services.yml
new file mode 100644
index 0000000..a66224b
--- /dev/null
+++ b/modules/magic_code_email_login/magic_code_email_login.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\magic_code_email_login\Hook\MagicCodeEmailLoginHooks:
+    class: Drupal\magic_code_email_login\Hook\MagicCodeEmailLoginHooks
+    autowire: true
diff --git a/modules/magic_code_email_login/src/Hook/MagicCodeEmailLoginHooks.php b/modules/magic_code_email_login/src/Hook/MagicCodeEmailLoginHooks.php
new file mode 100644
index 0000000..c98245b
--- /dev/null
+++ b/modules/magic_code_email_login/src/Hook/MagicCodeEmailLoginHooks.php
@@ -0,0 +1,102 @@
+<?php
+
+namespace Drupal\magic_code_email_login\Hook;
+
+use Drupal\Component\Render\PlainTextOutput;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Form\FormStateInterface;
+use Drupal\user\UserInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for magic_code_email_login.
+ */
+class MagicCodeEmailLoginHooks {
+
+  /**
+   * Implements hook_user_login().
+   *
+   * Clears the user based flood on user login.
+   */
+  #[Hook('user_login')]
+  public function userLogin(UserInterface $user) {
+    $userFloodConfig = \Drupal::config('user.flood');
+    $request = \Drupal::request();
+    if ($userFloodConfig->get('uid_only')) {
+      $identifier = $user->id();
+    }
+    else {
+      $identifier = $user->id() . '-' . $request->getClientIP();
+    }
+    // Clear user.
+    \Drupal::flood()->clear('magic_code_email_login.failed_user', $identifier);
+  }
+
+  /**
+   * Implements hook_form_user_admin_settings_alter().
+   *
+   * Make the magic code email configurable in the admin account settings.
+   */
+  #[Hook('form_user_admin_settings_alter')]
+  public function formUserAdminSettingsAlter(&$form, FormStateInterface $form_state, $form_id) {
+    $mailConfig = \Drupal::config('magic_code_email_login.mail');
+    $form['magic_code_email_login'] = [
+      '#type' => 'details',
+      '#title' => new TranslatableMarkup('Magic Code Login'),
+      '#collapsible' => TRUE,
+      '#collapsed' => TRUE,
+      '#group' => 'email',
+      '#weight' => 12,
+    ];
+    $form['magic_code_email_login']['magic_code_email_login_subject'] = [
+      '#type' => 'textfield',
+      '#title' => new TranslatableMarkup('Subject'),
+      '#default_value' => $mailConfig->get('login.subject'),
+      '#maxlength' => 180,
+    ];
+    $form['magic_code_email_login']['magic_code_email_login_body'] = [
+      '#type' => 'textarea',
+      '#title' => new TranslatableMarkup('Body'),
+      '#default_value' => $mailConfig->get('login.body'),
+      '#rows' => 12,
+    ];
+    $form['#submit'][] = '_magic_code_email_login_form_user_admin_settings_submit';
+  }
+
+  /**
+   * Implements hook_mail().
+   */
+  #[Hook('mail')]
+  public function mail($key, &$message, $params) {
+    $token_service = \Drupal::token();
+    $language_manager = \Drupal::languageManager();
+    $langcode = $message['langcode'];
+    $variables = [
+      'user' => $params['account'],
+    ];
+    $language = $language_manager->getLanguage($langcode);
+    $original_language = $language_manager->getConfigOverrideLanguage();
+    $language_manager->setConfigOverrideLanguage($language);
+    $mail_config = \Drupal::config('magic_code_email_login.mail');
+    $token_options = [
+      'langcode' => $langcode,
+      'callback' => 'user_mail_tokens',
+      'clear' => TRUE,
+    ];
+    $message['subject'] .= PlainTextOutput::renderFromHtml($token_service->replace($mail_config->get($key . '.subject'), $variables, $token_options));
+    $message['body'][] = $token_service->replace($mail_config->get($key . '.body'), $variables, $token_options);
+    $language_manager->setConfigOverrideLanguage($original_language);
+  }
+
+  /**
+   * Implements hook_magic_code_replace_token_in_email_alter().
+   */
+  #[Hook('magic_code_replace_token_in_email_alter')]
+  public function magicCodeReplaceTokenInEmailAlter(&$replaceInEmail, &$context) {
+    // Replace token in this module's login email.
+    if ($context['message']['id'] === 'magic_code_email_login_login') {
+      $replaceInEmail = TRUE;
+    }
+  }
+
+}
diff --git a/modules/magic_code_email_login/tests/src/Functional/MagicCodeLoginFormTest.php b/modules/magic_code_email_login/tests/src/Functional/MagicCodeLoginFormTest.php
index e24a940..d47b4aa 100644
--- a/modules/magic_code_email_login/tests/src/Functional/MagicCodeLoginFormTest.php
+++ b/modules/magic_code_email_login/tests/src/Functional/MagicCodeLoginFormTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code_email_login\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Test\AssertMailTrait;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\magic_code\MagicCodeManagerInterface;
@@ -11,6 +12,7 @@ use Drupal\magic_code\MagicCodeManagerInterface;
 /**
  * Test the magic code login form.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeLoginFormTest extends BrowserTestBase {
 
   use AssertMailTrait;
diff --git a/src/Hook/MagicCodeHooks.php b/src/Hook/MagicCodeHooks.php
new file mode 100644
index 0000000..ef44b84
--- /dev/null
+++ b/src/Hook/MagicCodeHooks.php
@@ -0,0 +1,163 @@
+<?php
+
+namespace Drupal\magic_code\Hook;
+
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\user\UserInterface;
+use Drupal\Core\Entity\EntityInterface;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for magic_code.
+ */
+class MagicCodeHooks {
+
+  /**
+   * Implements hook_cron().
+   */
+  #[Hook('cron')]
+  public function cron() {
+    /** @var \Drupal\magic_code\MagicCodeCollector $collector */
+    $collector = \Drupal::service('magic_code.collector');
+    $logger = \Drupal::service('logger.channel.magic_code');
+    $limit = 0;
+    // Delete a batch of expired codes.
+    if (!empty($expiredCodes = $collector->collectExpired($limit))) {
+      $count = count($expiredCodes);
+      $collector->deleteMultipleMagicCodes($expiredCodes);
+      $logger->notice('Deleted @count expired magic codes in cron.', [
+        '@count' => $count,
+      ]);
+    }
+  }
+
+  /**
+   * Implements hook_entity_update().
+   *
+   * Revokes all magic codes for updated user.
+   */
+  #[Hook('entity_update')]
+  public function entityUpdate(EntityInterface $entity) {
+    if (!$entity instanceof UserInterface) {
+      return;
+    }
+    /** @var \Drupal\magic_code\MagicCodeCollector $collector */
+    $collector = \Drupal::service('magic_code.collector');
+    $logger = \Drupal::service('logger.channel.magic_code');
+    if (!empty($codes = $collector->collectForAccount($entity))) {
+      $count = count($codes);
+      foreach ($codes as $code) {
+        $code->revoke()->save();
+      }
+      $logger->notice('Revoked @count magic codes for user @id due to user entity update.', [
+        '@count' => $count,
+        '@id' => $entity->id(),
+      ]);
+    }
+  }
+
+  /**
+   * Implements hook_mail_alter().
+   */
+  #[Hook('mail_alter')]
+  public function mailAlter(&$message) {
+    $replaceInEmail = FALSE;
+    $context = [
+      'message' => $message,
+    ];
+    // Alow user emails by default.
+    if (strpos($message['id'], 'user_') === 0) {
+      $replaceInEmail = TRUE;
+    }
+    // Let modules alter the decision.
+    \Drupal::moduleHandler()->alter('magic_code_replace_token_in_email', $replaceInEmail, $context);
+    if ($replaceInEmail) {
+      _magic_code_replace_mail_tokens($message);
+    }
+  }
+
+  /**
+   * Implements hook_FORM_ID_alter().
+   *
+   * Adds flood config options to config form of flood_control module.
+   */
+  #[Hook('form_flood_control_settings_form_alter')]
+  public function formFloodControlSettingsFormAlter(&$form) {
+    $floodConfigCreation = \Drupal::configFactory()->get('magic_code.settings')->get('flood_creation');
+    $floodConfigVerification = \Drupal::configFactory()->get('magic_code.settings')->get('flood_verification');
+    // Magic Code Creation.
+    $form['magic_code_creation'] = [
+      '#type' => 'fieldset',
+      '#title' => new TranslatableMarkup('Magic Code - Creation'),
+    ];
+    $form['magic_code_creation']['info'] = [
+      '#markup' => new TranslatableMarkup('Applies to whenever a magic code is being created. This is to prevent an attacker from creating loads of magic codes.'),
+    ];
+    $form['magic_code_creation']['magic_code_creation_ip_limit'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('IP limit'),
+      '#options' => $form['login']['ip_limit']['#options'],
+      '#default_value' => $floodConfigCreation['ip_limit'],
+      '#description' => new TranslatableMarkup('The allowed number of magic code creations from one IP address within the allowed time window.'),
+    ];
+    $form['magic_code_creation']['magic_code_creation_ip_window'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('IP time window'),
+      '#options' => $form['login']['ip_window']['#options'],
+      '#default_value' => $floodConfigCreation['ip_window'],
+      '#description' => new TranslatableMarkup('The allowed time window for magic code creations by IP.'),
+    ];
+    $form['magic_code_creation']['magic_code_creation_user_limit'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('User limit'),
+      '#options' => $form['login']['user_limit']['#options'],
+      '#default_value' => $floodConfigCreation['user_limit'],
+      '#description' => new TranslatableMarkup('The allowed number of magic code creations for a given user within the allowed time window.'),
+    ];
+    $form['magic_code_creation']['magic_code_creation_user_window'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('User time window'),
+      '#options' => $form['login']['user_window']['#options'],
+      '#default_value' => $floodConfigCreation['user_window'],
+      '#description' => new TranslatableMarkup('The allowed time window for magic code creations by user.'),
+    ];
+    // Magic Code Verification.
+    $form['magic_code_verification'] = [
+      '#type' => 'fieldset',
+      '#title' => new TranslatableMarkup('Magic Code - Verification'),
+    ];
+    $form['magic_code_verification']['info'] = [
+      '#markup' => new TranslatableMarkup('Applies to whenever a magic code is being verified.'),
+    ];
+    $form['magic_code_verification']['magic_code_verification_ip_limit'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('IP limit'),
+      '#options' => $form['login']['ip_limit']['#options'],
+      '#default_value' => $floodConfigVerification['ip_limit'],
+      '#description' => new TranslatableMarkup('The allowed number of magic code verifications from one IP address within the allowed time window.'),
+    ];
+    $form['magic_code_verification']['magic_code_verification_ip_window'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('IP time window'),
+      '#options' => $form['login']['ip_window']['#options'],
+      '#default_value' => $floodConfigVerification['ip_window'],
+      '#description' => new TranslatableMarkup('The allowed time window for magic code verifications by IP.'),
+    ];
+    $form['magic_code_verification']['magic_code_verification_user_limit'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('User limit'),
+      '#options' => $form['login']['user_limit']['#options'],
+      '#default_value' => $floodConfigVerification['user_limit'],
+      '#description' => new TranslatableMarkup('The allowed number of magic code verifications for a given user within the allowed time window.'),
+    ];
+    $form['magic_code_verification']['magic_code_verification_user_window'] = [
+      '#type' => 'select',
+      '#title' => new TranslatableMarkup('User time window'),
+      '#options' => $form['login']['user_window']['#options'],
+      '#default_value' => $floodConfigVerification['user_window'],
+      '#description' => new TranslatableMarkup('The allowed time window for magic code verifications by user.'),
+    ];
+    $form['#submit'][] = '_magic_code_form_flood_control_settings_submit';
+  }
+
+}
diff --git a/src/Hook/MagicCodeTokensHooks.php b/src/Hook/MagicCodeTokensHooks.php
new file mode 100644
index 0000000..bb19db8
--- /dev/null
+++ b/src/Hook/MagicCodeTokensHooks.php
@@ -0,0 +1,62 @@
+<?php
+
+namespace Drupal\magic_code\Hook;
+
+use Drupal\Core\Render\BubbleableMetadata;
+use Drupal\Core\StringTranslation\TranslatableMarkup;
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for magic_code.
+ */
+class MagicCodeTokensHooks {
+
+  /**
+   * Implements hook_token_info().
+   */
+  #[Hook('token_info')]
+  public function tokenInfo() {
+    $operations = _magic_code_get_available_tokens();
+    $tokens = [];
+    foreach ($operations as $operation) {
+      $key = 'magic-code-' . $operation;
+      $tokens[$key] = [
+        'name' => new TranslatableMarkup('Magic Code - @operation', [
+          '@operation' => $operation,
+        ]),
+        'description' => new TranslatableMarkup('A magic code with the operation "@operation"', [
+          '@operation' => $operation,
+        ]),
+        'restricted' => TRUE,
+      ];
+    }
+    return [
+      'tokens' => [
+        'user' => $tokens,
+      ],
+    ];
+  }
+
+  /**
+   * Implements hook_tokens().
+   */
+  #[Hook('tokens')]
+  public function tokens($type, $tokens, array $data, array $options, BubbleableMetadata $bubbleable_metadata) {
+    $replacements = [];
+    if ($type == 'user' && !empty($data['user'])) {
+      $operations = _magic_code_get_available_tokens();
+      foreach ($tokens as $name => $original) {
+        // Safety check.
+        if (!str_contains($name, 'magic-code-')) {
+          continue;
+        }
+        $operation = str_replace('magic-code-', '', $name);
+        if (in_array($operation, $operations)) {
+          $replacements[$original] = sprintf('[user:magic-code-%s_mail-only]', $operation);
+        }
+      }
+    }
+    return $replacements;
+  }
+
+}
diff --git a/tests/modules/magic_code_test/magic_code_test.module b/tests/modules/magic_code_test/magic_code_test.module
index a21eaac..d7ea633 100644
--- a/tests/modules/magic_code_test/magic_code_test.module
+++ b/tests/modules/magic_code_test/magic_code_test.module
@@ -7,18 +7,21 @@
 
 declare(strict_types=1);
 
+use Drupal\Core\Hook\Attribute\LegacyHook;
+use Drupal\magic_code_test\Hook\MagicCodeTestHooks;
+
 /**
  * Implements hook_magic_code_user_mail_token_operations_alter().
  */
+#[LegacyHook]
 function magic_code_test_magic_code_user_mail_token_operations_alter(&$operations) {
-  $operations[] = 'added-operation';
+  \Drupal::service(MagicCodeTestHooks::class)->magicCodeUserMailTokenOperationsAlter($operations);
 }
 
 /**
  * Implements hook_magic_code_replace_token_in_email_alter().
  */
+#[LegacyHook]
 function magic_code_test_magic_code_replace_token_in_email_alter(&$replaceInEmail, &$context) {
-  if ($context['message']['id'] === 'user_register_no_approval_required') {
-    $replaceInEmail = FALSE;
-  }
+  \Drupal::service(MagicCodeTestHooks::class)->magicCodeReplaceTokenInEmailAlter($replaceInEmail, $context);
 }
diff --git a/tests/modules/magic_code_test/magic_code_test.services.yml b/tests/modules/magic_code_test/magic_code_test.services.yml
new file mode 100644
index 0000000..f897a48
--- /dev/null
+++ b/tests/modules/magic_code_test/magic_code_test.services.yml
@@ -0,0 +1,5 @@
+
+services:
+  Drupal\magic_code_test\Hook\MagicCodeTestHooks:
+    class: Drupal\magic_code_test\Hook\MagicCodeTestHooks
+    autowire: true
diff --git a/tests/modules/magic_code_test/src/Hook/MagicCodeTestHooks.php b/tests/modules/magic_code_test/src/Hook/MagicCodeTestHooks.php
new file mode 100644
index 0000000..eb276c7
--- /dev/null
+++ b/tests/modules/magic_code_test/src/Hook/MagicCodeTestHooks.php
@@ -0,0 +1,30 @@
+<?php
+
+namespace Drupal\magic_code_test\Hook;
+
+use Drupal\Core\Hook\Attribute\Hook;
+
+/**
+ * Hook implementations for magic_code_test.
+ */
+class MagicCodeTestHooks {
+
+  /**
+   * Implements hook_magic_code_user_mail_token_operations_alter().
+   */
+  #[Hook('magic_code_user_mail_token_operations_alter')]
+  public function magicCodeUserMailTokenOperationsAlter(&$operations) {
+    $operations[] = 'added-operation';
+  }
+
+  /**
+   * Implements hook_magic_code_replace_token_in_email_alter().
+   */
+  #[Hook('magic_code_replace_token_in_email_alter')]
+  public function magicCodeReplaceTokenInEmailAlter(&$replaceInEmail, &$context) {
+    if ($context['message']['id'] === 'user_register_no_approval_required') {
+      $replaceInEmail = FALSE;
+    }
+  }
+
+}
diff --git a/tests/src/Functional/MagicCodeAdminTest.php b/tests/src/Functional/MagicCodeAdminTest.php
index 573dd5e..cfd7a66 100644
--- a/tests/src/Functional/MagicCodeAdminTest.php
+++ b/tests/src/Functional/MagicCodeAdminTest.php
@@ -4,12 +4,14 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\magic_code\MagicCodeManagerInterface;
 
 /**
  * Test the magic code admin forms.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeAdminTest extends BrowserTestBase {
 
   /**
diff --git a/tests/src/Functional/MagicCodeCommandsTest.php b/tests/src/Functional/MagicCodeCommandsTest.php
index 93334ec..29a9ff7 100644
--- a/tests/src/Functional/MagicCodeCommandsTest.php
+++ b/tests/src/Functional/MagicCodeCommandsTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Entity\EntityStorageInterface;
 use Drupal\Tests\BrowserTestBase;
 use Drupal\consumers\Entity\Consumer;
@@ -13,6 +14,7 @@ use Drush\TestTraits\DrushTestTrait;
 /**
  * Test MagicCode Drush Commands.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeCommandsTest extends BrowserTestBase {
 
   use DrushTestTrait;
diff --git a/tests/src/Functional/MagicCodeFloodControlFormTest.php b/tests/src/Functional/MagicCodeFloodControlFormTest.php
index c41057b..7b24661 100644
--- a/tests/src/Functional/MagicCodeFloodControlFormTest.php
+++ b/tests/src/Functional/MagicCodeFloodControlFormTest.php
@@ -4,11 +4,13 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Functional;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Tests\BrowserTestBase;
 
 /**
  * Test the magic code alteration of the flood control form.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeFloodControlFormTest extends BrowserTestBase {
 
   /**
diff --git a/tests/src/Kernel/MagicCodeManagerTest.php b/tests/src/Kernel/MagicCodeManagerTest.php
index d2952b9..65a85ca 100644
--- a/tests/src/Kernel/MagicCodeManagerTest.php
+++ b/tests/src/Kernel/MagicCodeManagerTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Kernel;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\consumers\Entity\Consumer;
 use Drupal\magic_code\MagicCodeManagerInterface;
@@ -12,6 +13,7 @@ use Drupal\magic_code\MagicCodeResult;
 /**
  * Tests the Magic Code Manager.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeManagerTest extends EntityKernelTestBase {
 
   /**
diff --git a/tests/src/Kernel/MagicCodeTokenTest.php b/tests/src/Kernel/MagicCodeTokenTest.php
index 1becdda..50ccbe4 100644
--- a/tests/src/Kernel/MagicCodeTokenTest.php
+++ b/tests/src/Kernel/MagicCodeTokenTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Kernel;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\Core\Test\AssertMailTrait;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\consumers\Entity\Consumer;
@@ -12,6 +13,7 @@ use Drupal\magic_code\MagicCodeManagerInterface;
 /**
  * Tests the Magic Code Tokens in user emails.
  */
+#[RunTestsInSeparateProcesses]
 class MagicCodeTokenTest extends EntityKernelTestBase {
 
   use AssertMailTrait;
diff --git a/tests/src/Kernel/VerificationProviderTest.php b/tests/src/Kernel/VerificationProviderTest.php
index 96e99d9..693cec8 100644
--- a/tests/src/Kernel/VerificationProviderTest.php
+++ b/tests/src/Kernel/VerificationProviderTest.php
@@ -4,6 +4,7 @@ declare(strict_types=1);
 
 namespace Drupal\Tests\magic_code\Kernel;
 
+use PHPUnit\Framework\Attributes\RunTestsInSeparateProcesses;
 use Drupal\KernelTests\Core\Entity\EntityKernelTestBase;
 use Drupal\Tests\verification\Traits\VerificationTestTrait;
 use Drupal\consumers\Entity\Consumer;
@@ -14,6 +15,7 @@ use Symfony\Component\HttpFoundation\Request;
 /**
  * Tests the Magic Code Verification Provider.
  */
+#[RunTestsInSeparateProcesses]
 class VerificationProviderTest extends EntityKernelTestBase {
 
   use VerificationTestTrait;
