2fa on frontend Login

Used cookbook Tutorial for Customer Login in frontend. Works Fine!
Now i activated 2fa via Mail in config, works for Login via Panel. But not on frontend, Herr the Mail with Code Vers Not delivered…

Do you have a hint?


<?php

return function ($kirby) {

  // don't show the login screen to already logged in users
  if ($kirby->user()) {
    go('/panel');
  }

  $error = false;
  $step = 'login';
  $email = get('email');
  $session = $kirby->session();

  // Step 1: Handle login form
  if ($kirby->request()->is('POST') && get('login')) {

    // try to log the user in with the provided credentials
    try {
      $user = $kirby->user($email);
      if (!$user) throw new Exception('User not found');
      // Validate password only, do not log in
      if (method_exists($user, 'validatePassword')) {
        $valid = $user->validatePassword(get('password'));
      } else {
        // Fallback for older Kirby: check hash manually
        $valid = password_verify(get('password'), $user->password());
      }
      if ($valid) {
        // Use Kirby's official 2FA
        $user->send2faCode();
        $session->set('2fa.email', $email);
        $session->set('2fa.password', get('password'));
        $step = '2fa';
      } else {
        $error = 'Falsches Passwort.';
      }
    } catch (Exception $e) {
      $error = $e->getMessage();
    }

  }

  // Step 2: Handle 2FA code form
  if ($kirby->request()->is('POST') && get('verify')) {
    $email = $session->get('2fa.email');
    $password = $session->get('2fa.password');
    $user = $kirby->user($email);
    if ($user && $password) {
      try {
        if ($user->verify2faCode(get('code'))) {
          $kirby->auth()->login($email, $password);
          $session->remove('2fa.email');
          $session->remove('2fa.password');
          go('/panel');
        } else {
          $error = 'Der Code ist ungültig oder abgelaufen.';
          $step = '2fa';
        }
      } catch (Exception $e) {
        $error = $e->getMessage();
        $step = '2fa';
      }
    } else {
      $error = 'Unbekannter Benutzer.';
      $step = 'login';
    }
  }
  // Resend code logic
  if (get('resend') && $session->get('2fa.email')) {
    $email = $session->get('2fa.email');
    $user = $kirby->user($email);
    if ($user) {
      try {
        $user->send2faCode();
        $step = '2fa';
      } catch (Exception $e) {
        $error = $e->getMessage();
      }
    }
  }

  return [
    'error' => $error,
    'step' => $step,
    'email' => $email
  ];

};

What is that, a custom method?

See here how to use login with 2FA: Frontend login | Kirby CMS

Yes, i have seen that but did not understand the link, could you give me and advice or are there examples?

i reworked the login.php controller:

<?php

return function ($kirby) {

  // don't show the login screen to already logged in users
  if ($kirby->user()) {
    go('/panel');
  }

  $error = false;
  $step = 'login';
  $email_param = get('email'); // Renamed to avoid conflict with $email variable later
  $session = $kirby->session();

  // Step 1: Handle login form
  if ($kirby->request()->is('POST') && get('login')) {
    $email = $email_param; // Use the email from the form
    $password = get('password');

    try {
      // Attempt to log in. If 2FA is required, this will throw an AuthException.
      $kirby->auth()->login($email, $password);

      // If login is successful without 2FA (should not happen with current config but good practice)
      $session->remove('2fa.email'); // Clean up session just in case
      go('/panel');

    } catch (Kirby\Exception\AuthException $e) {
      if ($e->isChallenge()) {
        // 2FA is required. Kirby's Auth system should have sent the code.
        $session->set('2fa.email', $email); // Store email for the next step
        $step = '2fa';
      } elseif ($e->getType() === 'user') {
        $error = 'Benutzer nicht gefunden (AuthException).';
      } elseif ($e->getType() === 'password') {
        $error = 'Falsches Passwort (AuthException).';
      } else {
        // Other AuthException types, not a challenge
        $error = 'Authentifizierungsfehler: Typ ' . $e->getType() . ' - ' . $e->getMessage();
      }
    } catch (Exception $e) {
      // Other unexpected errors (NOT AuthException)
      $error = 'Allgemeiner Fehler: ' . $e->getMessage() . ' (Typ: ' . get_class($e) . ')';
    }
  }

  // Step 2: Handle 2FA code form
  if ($kirby->request()->is('POST') && get('verify')) {
    $email_from_session = $session->get('2fa.email');
    $code = get('code');

    if ($email_from_session && $code) {
      try {
        // Attempt to log in using the email from session and the 2FA code. Password is null.
        $kirby->auth()->login($email_from_session, null, $code);

        // Login successful
        $session->remove('2fa.email');
        go('/panel'); 

      } catch (Kirby\Exception\AuthException $e) {
        // Invalid code or other auth issue
        $error = 'Der Code ist ungültig oder abgelaufen.';
        $step = '2fa'; // Stay on 2FA step
      } catch (Exception $e) {
        $error = $e->getMessage();
        $step = '2fa';
      }
    } else {
      // Session expired or code not provided
      $error = 'Sitzung abgelaufen oder Code fehlt. Bitte versuchen Sie es erneut.';
      $step = 'login'; // Send back to login step
      $session->remove('2fa.email'); // Clear session
    }
  }

  // Resend code logic
  if ($kirby->request()->is('POST') && get('resend')) { // Ensure it's a POST request for resend
    $email_from_session = $session->get('2fa.email');
    if ($email_from_session) {
      $user = $kirby->user($email_from_session);
      if ($user) {
        try {
          $user->send2faCode(); // This should use the configured template
          // Optionally, add a success message here if your template supports it
          // $success = 'Ein neuer Code wurde gesendet.';
          $step = '2fa';
        } catch (Exception $e) {
          $error = 'Fehler beim Senden des Codes: ' . $e->getMessage();
          $step = '2fa'; // Stay on 2FA step, show error
        }
      } else {
        $error = 'Benutzer für erneutes Senden nicht gefunden.';
        $step = 'login';
        $session->remove('2fa.email');
      }
    } else {
        $error = 'Sitzung für erneutes Senden abgelaufen.';
        $step = 'login';
    }
  }

  return [
    'error' => $error,
    'step' => $step,
    'email' => $email_param // Pass the original email attempt back to the form
  ];

};

could you have a look?
now the 2fa is not asked, the login workes with just mail and pw; even i have set it true global on the config. in the /panel it woirks but not on the /login frontend

This does a normal login without 2FA challenge.

See the docs I linked to, you have to use the login2Fa() method here, instead of the login method! And then the $kirby->auth()-> verifyChallenge()` method after the code is entered.

1 Like

i’m sure my questions an approaches seem nooby so thanks. you helped a lot and its working!