diff --git a/app/views/tfa/setup.php b/app/views/tfa/setup.php
index 6522bd099a2a59cd079a800c8b1f1216bc082ba7..5c605597ec2eb6351bf2687f2c9aab8834a8b6c4 100644
--- a/app/views/tfa/setup.php
+++ b/app/views/tfa/setup.php
@@ -4,12 +4,7 @@
     <fieldset>
         <legend><?= _('Zwei-Faktor-Authentifizierung einrichten') ?></legend>
 
-        <p>
-            <?= _('Mittels Zwei-Faktor-Authentifizierung können Sie Ihr Konto schützen, '
-                . 'indem bei jedem Login ein Token von Ihnen eingegeben werden muss.') ?>
-            <?= _('Dieses Token erhalten Sie entweder per E-Mail oder können es über '
-                . 'eine geeignete Authenticator-App erzeugen lassen.') ?>
-        </p>
+        <?= formatReady(Config::get()->TFA_TEXT_INTRODUCTION) ?>
 
         <label>
             <input required type="radio" name="type" value="email">
diff --git a/db/migrations/5.1.5_tfa_improved_texts.php b/db/migrations/5.1.5_tfa_improved_texts.php
new file mode 100644
index 0000000000000000000000000000000000000000..9ddaf8fd2bf7977542cb1d9c31962a3e9de7e732
--- /dev/null
+++ b/db/migrations/5.1.5_tfa_improved_texts.php
@@ -0,0 +1,93 @@
+<?php
+final class TfaImprovedTexts extends Migration
+{
+    public function description()
+    {
+        return 'TIC #11510: Improve texts for two factor authentication';
+    }
+
+    protected function up()
+    {
+        $query = "INSERT IGNORE INTO `config` (
+                    `field`, `value`, `type`, `range`, `section`,
+                    `mkdate`, `chdate`, `description`
+                  ) VALUES(
+                    :id, :text, 'i18n', 'global', 'Zwei-Faktor-Authentifizierung',
+                    UNIX_TIMESTAMP(), UNIX_TIMESTAMP(), :description
+                  )";
+        DBManager::get()->execute($query, [
+            ':id'          => 'TFA_TEXT_INTRODUCTION',
+            ':text'        => $this->getIntroductionText('de'),
+            ':description' => 'Text, der als Einleitung beim Einrichten der Zwei-Faktor-Authentisierung angezeigt wird',
+        ]);
+        DBManager::get()->execute($query, [
+            ':id'          => 'TFA_TEXT_APP',
+            ':text'        => $this->getAppText('de'),
+            ':description' => 'Text, der als Einleitung beim Einrichten der Zwei-Faktor-Authentisierung via App angezeigt wird',
+        ]);
+
+        $query = "INSERT IGNORE INTO `i18n` (`object_id`, `table`, `field`, `lang`, `value`)
+                  VALUES (MD5(:id), 'config', 'value', 'en_GB', :text)";
+        DBManager::get()->execute($query, [
+            ':id'   => 'TFA_TEXT_INTRODUCTION',
+            ':text' => $this->getIntroductionText('en'),
+        ]);
+        DBManager::get()->execute($query, [
+            ':id'   => 'TFA_TEXT_APP',
+            ':text' => $this->getAppText('en'),
+        ]);
+    }
+
+    protected function down()
+    {
+        $query = "DELETE `config`, `config_values`
+                  FROM `config`
+                  LEFT JOIN `config_values` USING (`field`)
+                  WHERE `field` IN ('TFA_TEXT_INTRODUCTION', 'TFA_TEXT_APP')";
+        DBManager::get()->exec($query);
+
+        $query = "DELETE FROM `i18n`
+                  WHERE `object_id` IN (MD5('TFA_TEXT_INTRODUCTION'), MD5('TFA_TEXT_APP'))
+                    AND `table` = 'config'
+                    AND `field` = 'value'";
+        DBManager::get()->exec($query);
+    }
+
+    private function getIntroductionText($language)
+    {
+        if ($language === 'en') {
+            return 'Using two-factor authentication you can protect your account by '
+                 . 'entering a token on each login. '
+                 . 'You get that token either via E-Mail or by using an appropriate '
+                 . 'authenticator app.';
+        } else {
+            return 'Mittels Zwei-Faktor-Authentifizierung können Sie Ihr Konto schützen, '
+                 . 'indem bei jedem Login ein Token von Ihnen eingegeben werden muss. '
+                 . 'Dieses Token erhalten Sie entweder per E-Mail oder können es über '
+                 . 'eine geeignete Authenticator-App erzeugen lassen.';
+        }
+    }
+
+    private function getAppText($language)
+    {
+        if ($language === 'en') {
+            $result = "Set up a suitable OTP authenticator app for this purpose. "
+                    . "Here you will find a list of known and compatible apps:\n";
+        } else {
+            $result = "Richten Sie dafür eine geeignete OTP-Authenticator-App ein. Hier "
+                    . "finden Sie eine Liste bekannter und kompatibler Apps:\n";
+        }
+        return $result . $this->getAuthenticatorList();
+    }
+
+    private function getAuthenticatorList()
+    {
+        return implode("\n", [
+            '- [Authy]https://authy.com/',
+            '- [FreeOTP]https://freeotp.github.io/',
+            '- Google Authenticator: [Android]https://play.google.com/store/apps/details?id=com.google.android.apps.authenticator2 oder [iOS]https://apps.apple.com/app/google-authenticator/id388497605',
+            '- [LastPass Authenticator]https://lastpass.com/auth/',
+            '- [Microsoft Authenticator]https://www.microsoft.com/authenticator',
+        ]);
+    }
+}
diff --git a/db/migrations/5.1.6_tfa_trust_duration.php b/db/migrations/5.1.6_tfa_trust_duration.php
new file mode 100644
index 0000000000000000000000000000000000000000..55d7d75916387d94e04b9d2d64d4ea9e343b6a97
--- /dev/null
+++ b/db/migrations/5.1.6_tfa_trust_duration.php
@@ -0,0 +1,32 @@
+<?php
+final class TfaTrustDuration extends Migration
+{
+    public function description()
+    {
+        return 'TIC #11508: Configurable trust duration for devices';
+    }
+
+    protected function up()
+    {
+        // Create course config
+        $query = "INSERT IGNORE INTO `config` (
+                    `field`, `value`, `type`, `range`, `section`,
+                    `mkdate`, `chdate`,
+                    `description`
+                  ) VALUES(
+                    'TFA_TRUST_DURATION', '30', 'integer', 'global', 'Zwei-Faktor-Authentifizierung',
+                    UNIX_TIMESTAMP(), UNIX_TIMESTAMP(),
+                    'Dauer, denen Geräte vertraut werden soll in Tagen (0 für dauerhaftes Vertrauen)'
+                  )";
+        DBManager::get()->exec($query);
+    }
+
+    protected function down()
+    {
+        $query = "DELETE `config`, `config_values`
+                  FROM `config`
+                  LEFT JOIN `config_values` USING (`field`)
+                  WHERE `field` = 'TFA_TRUST_DURATION'";
+        DBManager::get()->exec($query);
+    }
+}
diff --git a/lib/classes/TwoFactorAuth.php b/lib/classes/TwoFactorAuth.php
index f66b66784da4f9d2ac2befc0f27a71a4c464ada5..ef7e6685aa8705d154420df73934e02e8e989ea4 100644
--- a/lib/classes/TwoFactorAuth.php
+++ b/lib/classes/TwoFactorAuth.php
@@ -221,9 +221,10 @@ final class TwoFactorAuth
         echo $GLOBALS['template_factory']->render(
             'tfa-validate.php',
             $_SESSION[self::SESSION_DATA] + [
-                'secret'  => $this->secret,
-                'text'    => $text,
-                'blocked' => $this->isBlocked(),
+                'secret'   => $this->secret,
+                'text'     => $text,
+                'blocked'  => $this->isBlocked(),
+                'duration' => Config::get()->TFA_TRUST_DURATION,
             ],
             'layouts/base.php'
         );
@@ -250,11 +251,14 @@ final class TwoFactorAuth
      */
     private function registerSecretInCookie()
     {
+        $lifetime_in_days = Config::get()->TFA_TRUST_DURATION;
+        $lifetime = $lifetime_in_days > 0 ? strtotime("+{$lifetime_in_days} days") : 2147483647;
+
         $timeslice = mt_rand(0, PHP_INT_MAX);
         setcookie(
             self::COOKIE_KEY,
             implode(':', [$this->secret->getToken($timeslice), $timeslice]),
-            strtotime('+30 days'),
+            $lifetime,
             $GLOBALS['CANONICAL_RELATIVE_PATH_STUDIP']
         );
     }
diff --git a/templates/tfa-validate.php b/templates/tfa-validate.php
index 4770a67c31c054b74399f7653c2217df3765e5d1..62a5eb3f25e90738f7c8af12ded3c014a36b7ab1 100644
--- a/templates/tfa-validate.php
+++ b/templates/tfa-validate.php
@@ -11,6 +11,7 @@
 <? else: ?>
         <p><?= htmlReady($text ?: _('Bitte geben Sie ein gültiges Token ein')) ?></p>
     <? if ($secret->type === 'app' && !$secret->confirmed): ?>
+        <?= formatReady(Config::get()->TFA_TEXT_APP) ?>
         <p>
             <?= _('Scannen Sie diesen Code mit Ihrer App ein und geben Sie '
                 . 'anschliessend ein gültiges Token ein.') ?>
@@ -53,7 +54,18 @@
     <? if ($global): ?>
         <label>
             <input type="checkbox" name="tfa-trusted" value="1">
-            <?= _('Diesem Gerät für 30 Tage vertrauen.') ?>
+        <? if ($duration > 0): ?>
+            <?= sprintf(
+                ngettext(
+                    'Diesem Gerät für %u Tag vertrauen.',
+                    'Diesem Gerät für %u Tage vertrauen.',
+                    $duration
+                ),
+                $duration
+            ) ?>
+        <? else: ?>
+            <?= _('Diesem Gerät dauerhaft vertrauen') ?>
+        <? endif; ?>
         </label>
     <? endif; ?>
     </fieldset>