diff --git a/core/Controller/TwoFactorChallengeController.php b/core/Controller/TwoFactorChallengeController.php
index 7405e66cdfc45f2f613956b43c920aa4931826cf..e2a0b5423ab6321c159c7d9d4f453b311e3e10ff 100644
--- a/core/Controller/TwoFactorChallengeController.php
+++ b/core/Controller/TwoFactorChallengeController.php
@@ -32,6 +32,7 @@ use OC_Util;
 use OCP\AppFramework\Controller;
 use OCP\AppFramework\Http\RedirectResponse;
 use OCP\AppFramework\Http\StandaloneTemplateResponse;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\Authentication\TwoFactorAuth\IProvidesCustomCSP;
 use OCP\Authentication\TwoFactorAuth\TwoFactorException;
@@ -107,6 +108,7 @@ class TwoFactorChallengeController extends Controller {
 		$providerSet = $this->twoFactorManager->getProviderSet($user);
 		$allProviders = $providerSet->getProviders();
 		list($providers, $backupProvider) = $this->splitProvidersAndBackupCodes($allProviders);
+		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
 
 		$data = [
 			'providers' => $providers,
@@ -114,6 +116,7 @@ class TwoFactorChallengeController extends Controller {
 			'providerMissing' => $providerSet->isProviderMissing(),
 			'redirect_url' => $redirect_url,
 			'logout_url' => $this->getLogoutUrl(),
+			'hasSetupProviders' => !empty($setupProviders),
 		];
 		return new StandaloneTemplateResponse($this->appName, 'twofactorselectchallenge', $data, 'guest');
 	}
@@ -131,6 +134,7 @@ class TwoFactorChallengeController extends Controller {
 		$user = $this->userSession->getUser();
 		$providerSet = $this->twoFactorManager->getProviderSet($user);
 		$provider = $providerSet->getProvider($challengeProviderId);
+
 		if (is_null($provider)) {
 			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
 		}
@@ -209,4 +213,67 @@ class TwoFactorChallengeController extends Controller {
 		]));
 	}
 
+	/**
+	 * @NoAdminRequired
+	 * @NoCSRFRequired
+	 */
+	public function setupProviders() {
+		$user = $this->userSession->getUser();
+		$setupProviders = $this->twoFactorManager->getLoginSetupProviders($user);
+
+		$data = [
+			'providers' => $setupProviders,
+			'logout_url' => $this->getLogoutUrl(),
+		];
+
+		$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupselection', $data, 'guest');
+		return $response;
+	}
+
+	/**
+	 * @NoAdminRequired
+	 * @NoCSRFRequired
+	 */
+	public function setupProvider(string $providerId) {
+		$user = $this->userSession->getUser();
+		$providers = $this->twoFactorManager->getLoginSetupProviders($user);
+
+		$provider = null;
+		foreach ($providers as $p) {
+			if ($p->getId() === $providerId) {
+				$provider = $p;
+				break;
+			}
+		}
+
+		if ($provider === null) {
+			return new RedirectResponse($this->urlGenerator->linkToRoute('core.TwoFactorChallenge.selectChallenge'));
+		}
+
+		/** @var IActivatableAtLogin $provider */
+		$tmpl = $provider->getLoginSetup($user)->getBody();
+		$data = [
+			'provider' => $provider,
+			'logout_url' => $this->getLogoutUrl(),
+			'template' => $tmpl->fetchPage(),
+		];
+		$response = new StandaloneTemplateResponse($this->appName, 'twofactorsetupchallenge', $data, 'guest');
+		return $response;
+	}
+
+	/**
+	 * @NoAdminRequired
+	 * @NoCSRFRequired
+	 *
+	 * @todo handle the extreme edge case of an invalid provider ID and redirect to the provider selection page
+	 */
+	public function confirmProviderSetup(string $providerId) {
+		return new RedirectResponse($this->urlGenerator->linkToRoute(
+			'core.TwoFactorChallenge.showChallenge',
+			[
+				'challengeProviderId' => $providerId,
+			]
+		));
+	}
+
 }
diff --git a/core/Middleware/TwoFactorMiddleware.php b/core/Middleware/TwoFactorMiddleware.php
index 167545b0df9413b3ecda5e7014663b840e6efcb1..7b32c0dd8959f855cdc5e8aedaaab52ad32bca46 100644
--- a/core/Middleware/TwoFactorMiddleware.php
+++ b/core/Middleware/TwoFactorMiddleware.php
@@ -36,6 +36,7 @@ use OCP\AppFramework\Controller;
 use OCP\AppFramework\Http\RedirectResponse;
 use OCP\AppFramework\Middleware;
 use OCP\AppFramework\Utility\IControllerMethodReflector;
+use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
 use OCP\IRequest;
 use OCP\ISession;
 use OCP\IURLGenerator;
@@ -87,6 +88,12 @@ class TwoFactorMiddleware extends Middleware {
 			return;
 		}
 
+		if ($controller instanceof ALoginSetupController
+			&& $this->userSession->getUser() !== null
+			&& $this->twoFactorManager->needsSecondFactor($this->userSession->getUser())) {
+			return;
+		}
+
 		if ($controller instanceof LoginController && $methodName === 'logout') {
 			// Don't block the logout page, to allow canceling the 2FA
 			return;
@@ -95,7 +102,6 @@ class TwoFactorMiddleware extends Middleware {
 		if ($this->userSession->isLoggedIn()) {
 			$user = $this->userSession->getUser();
 
-
 			if ($this->session->exists('app_password') || $this->twoFactorManager->isTwoFactorAuthenticated($user)) {
 				$this->checkTwoFactor($controller, $methodName, $user);
 			} else if ($controller instanceof TwoFactorChallengeController) {
diff --git a/core/routes.php b/core/routes.php
index 1544fd67e070ddf95a1b9e0555156a0f8cad024a..79db0c6ccdfe7b81aa209932f29fd19af2d1ff64 100644
--- a/core/routes.php
+++ b/core/routes.php
@@ -67,6 +67,9 @@ $application->registerRoutes($this, [
 		['name' => 'TwoFactorChallenge#selectChallenge', 'url' => '/login/selectchallenge', 'verb' => 'GET'],
 		['name' => 'TwoFactorChallenge#showChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'GET'],
 		['name' => 'TwoFactorChallenge#solveChallenge', 'url' => '/login/challenge/{challengeProviderId}', 'verb' => 'POST'],
+		['name' => 'TwoFactorChallenge#setupProviders', 'url' => 'login/setupchallenge', 'verb' => 'GET'],
+		['name' => 'TwoFactorChallenge#setupProvider', 'url' => 'login/setupchallenge/{providerId}', 'verb' => 'GET'],
+		['name' => 'TwoFactorChallenge#confirmProviderSetup', 'url' => 'login/setupchallenge/{providerId}', 'verb' => 'POST'],
 		['name' => 'OCJS#getConfig', 'url' => '/core/js/oc.js', 'verb' => 'GET'],
 		['name' => 'Preview#getPreviewByFileId', 'url' => '/core/preview', 'verb' => 'GET'],
 		['name' => 'Preview#getPreview', 'url' => '/core/preview.png', 'verb' => 'GET'],
diff --git a/core/templates/twofactorselectchallenge.php b/core/templates/twofactorselectchallenge.php
index 65691f5857db4e593a1cce2ab6044cff0356184a..8508039268ec792ec1f20facccd212a123b2a293 100644
--- a/core/templates/twofactorselectchallenge.php
+++ b/core/templates/twofactorselectchallenge.php
@@ -15,9 +15,20 @@ $noProviders = empty($_['providers']);
 	<img class="two-factor-icon" src="<?php p(image_path('core', 'actions/password-white.svg')) ?>" alt="" />
 	<p>
 		<?php if (is_null($_['backupProvider'])): ?>
-		<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?></strong>
+			<?php if (!$_['hasSetupProviders']) { ?>
+				<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Contact your admin for assistance.')) ?></strong>
+			<?php } else { ?>
+				<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Please continue to setup two-factor authentication.')) ?></strong>
+				<a class="button primary two-factor-primary" href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.setupProviders',
+					[
+						'redirect_url' => $_['redirect_url'],
+					]
+				)) ?>">
+					<?php p($l->t('Set up two-factor authentication')) ?>
+				</a>
+			<?php } ?>
 		<?php else: ?>
-		<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Use one of your backup codes to log in or contact your admin for assistance.')) ?></strong>
+			<strong><?php p($l->t('Two-factor authentication is enforced but has not been configured on your account. Use one of your backup codes to log in or contact your admin for assistance.')) ?></strong>
 		<?php endif; ?>
 	</p>
 	<?php else: ?>
diff --git a/core/templates/twofactorsetupchallenge.php b/core/templates/twofactorsetupchallenge.php
new file mode 100644
index 0000000000000000000000000000000000000000..9c182db17150f38937aa4dee8f0352f93c18d3f1
--- /dev/null
+++ b/core/templates/twofactorsetupchallenge.php
@@ -0,0 +1,16 @@
+<?php
+/** @var $l \OCP\IL10N */
+/** @var $_ array */
+/* @var $provider OCP\Authentication\TwoFactorAuth\IProvider */
+$provider = $_['provider'];
+/* @var $template string */
+$template = $_['template'];
+?>
+
+<div class="body-login-container update">
+	<h2 class="two-factor-header"><?php p($provider->getDisplayName()); ?></h2>
+	<?php print_unescaped($template); ?>
+	<p><a class="two-factor-secondary" href="<?php print_unescaped($_['logout_url']); ?>">
+			<?php p($l->t('Cancel log in')) ?>
+	</a></p>
+</div>
diff --git a/core/templates/twofactorsetupselection.php b/core/templates/twofactorsetupselection.php
new file mode 100644
index 0000000000000000000000000000000000000000..7d689b89af70a84ec22acd89df07b819a24d92f5
--- /dev/null
+++ b/core/templates/twofactorsetupselection.php
@@ -0,0 +1,58 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+?>
+<div class="body-login-container update">
+	<h2 class="two-factor-header"><?php p($l->t('Setup two-factor authentication')) ?></h2>
+	<?php p($l->t('Enhanced security is enforced for your account. Choose wich provider to set up:')) ?>
+	<ul>
+	<?php foreach ($_['providers'] as $provider): ?>
+		<li>
+			<a class="two-factor-provider"
+			   href="<?php p(\OC::$server->getURLGenerator()->linkToRoute('core.TwoFactorChallenge.setupProvider',
+								[
+									'providerId' => $provider->getId(),
+									'redirect_url' => $_['redirect_url'],
+								]
+							)) ?>">
+				<?php
+				if ($provider instanceof \OCP\Authentication\TwoFactorAuth\IProvidesIcons) {
+					$icon = $provider->getLightIcon();
+				} else {
+					$icon = image_path('core', 'actions/password-white.svg');
+				}
+				?>
+				<img src="<?php p($icon) ?>" alt="" />
+				<div>
+					<h3><?php p($provider->getDisplayName()) ?></h3>
+					<p><?php p($provider->getDescription()) ?></p>
+				</div>
+			</a>
+		</li>
+	<?php endforeach; ?>
+	</ul>
+	<p><a class="two-factor-secondary" href="<?php print_unescaped($_['logout_url']); ?>">
+		<?php p($l->t('Cancel log in')) ?>
+	</a></p>
+</div>
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 1d71a209dda448e6b29ab25ad77c8ccf7bb87e01..e8bdc89515dbed720eccca22cd55e368c3cf5d75 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -77,8 +77,11 @@ return array(
     'OCP\\Authentication\\IApacheBackend' => $baseDir . '/lib/public/Authentication/IApacheBackend.php',
     'OCP\\Authentication\\LoginCredentials\\ICredentials' => $baseDir . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
     'OCP\\Authentication\\LoginCredentials\\IStore' => $baseDir . '/lib/public/Authentication/LoginCredentials/IStore.php',
+    'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
+    'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
     'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
     'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php',
+    'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php',
     'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php',
     'OCP\\Authentication\\TwoFactorAuth\\IProvider' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvider.php',
     'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => $baseDir . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index 1628c87632357bf47bb9f88a692b40579af23fd3..d5dccfc102835758f6a36c6b394aeef85d905657 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -107,8 +107,11 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\Authentication\\IApacheBackend' => __DIR__ . '/../../..' . '/lib/public/Authentication/IApacheBackend.php',
         'OCP\\Authentication\\LoginCredentials\\ICredentials' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/ICredentials.php',
         'OCP\\Authentication\\LoginCredentials\\IStore' => __DIR__ . '/../../..' . '/lib/public/Authentication/LoginCredentials/IStore.php',
+        'OCP\\Authentication\\TwoFactorAuth\\ALoginSetupController' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php',
+        'OCP\\Authentication\\TwoFactorAuth\\IActivatableAtLogin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php',
         'OCP\\Authentication\\TwoFactorAuth\\IActivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IActivatableByAdmin.php',
         'OCP\\Authentication\\TwoFactorAuth\\IDeactivatableByAdmin' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IDeactivatableByAdmin.php',
+        'OCP\\Authentication\\TwoFactorAuth\\ILoginSetupProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php',
         'OCP\\Authentication\\TwoFactorAuth\\IPersonalProviderSettings' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IPersonalProviderSettings.php',
         'OCP\\Authentication\\TwoFactorAuth\\IProvider' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvider.php',
         'OCP\\Authentication\\TwoFactorAuth\\IProvidesCustomCSP' => __DIR__ . '/../../..' . '/lib/public/Authentication/TwoFactorAuth/IProvidesCustomCSP.php',
diff --git a/lib/private/Authentication/Login/TwoFactorCommand.php b/lib/private/Authentication/Login/TwoFactorCommand.php
index 2825dc1763fae8a3c3ef46451856cb6b8bdd4ab6..7a1daa0ad50a8b851dab5444f2e93caf04109e7b 100644
--- a/lib/private/Authentication/Login/TwoFactorCommand.php
+++ b/lib/private/Authentication/Login/TwoFactorCommand.php
@@ -28,6 +28,7 @@ namespace OC\Authentication\Login;
 use function array_pop;
 use function count;
 use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\IURLGenerator;
 
@@ -36,12 +37,17 @@ class TwoFactorCommand extends ALoginCommand {
 	/** @var Manager */
 	private $twoFactorManager;
 
+	/** @var MandatoryTwoFactor */
+	private $mandatoryTwoFactor;
+
 	/** @var IURLGenerator */
 	private $urlGenerator;
 
 	public function __construct(Manager $twoFactorManager,
+								MandatoryTwoFactor $mandatoryTwoFactor,
 								IURLGenerator $urlGenerator) {
 		$this->twoFactorManager = $twoFactorManager;
+		$this->mandatoryTwoFactor = $mandatoryTwoFactor;
 		$this->urlGenerator = $urlGenerator;
 	}
 
@@ -52,9 +58,18 @@ class TwoFactorCommand extends ALoginCommand {
 
 		$this->twoFactorManager->prepareTwoFactorLogin($loginData->getUser(), $loginData->isRememberLogin());
 
-		$providers = $this->twoFactorManager->getProviderSet($loginData->getUser())->getPrimaryProviders();
-		if (count($providers) === 1) {
-			// Single provider, hence we can redirect to that provider's challenge page directly
+		$providerSet = $this->twoFactorManager->getProviderSet($loginData->getUser());
+		$loginProviders = $this->twoFactorManager->getLoginSetupProviders($loginData->getUser());
+		$providers = $providerSet->getPrimaryProviders();
+		if (empty($providers)
+			&& !$providerSet->isProviderMissing()
+			&& !empty($loginProviders)
+			&& $this->mandatoryTwoFactor->isEnforcedFor($loginData->getUser())) {
+			// No providers set up, but 2FA is enforced and setup providers are available
+			$url = 'core.TwoFactorChallenge.setupProviders';
+			$urlParams = [];
+		} else if (!$providerSet->isProviderMissing() && count($providers) === 1) {
+			// Single provider (and no missing ones), hence we can redirect to that provider's challenge page directly
 			/* @var $provider IProvider */
 			$provider = array_pop($providers);
 			$url = 'core.TwoFactorChallenge.showChallenge';
diff --git a/lib/private/Authentication/TwoFactorAuth/Manager.php b/lib/private/Authentication/TwoFactorAuth/Manager.php
index ef95184aba7c92c56bece9d5de49a20cf6e22591..17f8479f25762ac30e169ed2444cc96930019d4e 100644
--- a/lib/private/Authentication/TwoFactorAuth/Manager.php
+++ b/lib/private/Authentication/TwoFactorAuth/Manager.php
@@ -36,6 +36,8 @@ use OC\Authentication\Exceptions\InvalidTokenException;
 use OC\Authentication\Token\IProvider as TokenProvider;
 use OCP\Activity\IManager;
 use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
+use OCP\Authentication\TwoFactorAuth\ILoginSetupProvider;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\Authentication\TwoFactorAuth\IRegistry;
 use OCP\IConfig;
@@ -133,6 +135,18 @@ class Manager {
 		return $providers[$challengeProviderId] ?? null;
 	}
 
+	/**
+	 * @param IUser $user
+	 * @return IActivatableAtLogin[]
+	 * @throws Exception
+	 */
+	public function getLoginSetupProviders(IUser $user): array {
+		$providers = $this->providerLoader->getProviders($user);
+		return array_filter($providers, function(IProvider $provider) {
+			return ($provider instanceof IActivatableAtLogin);
+		});
+	}
+
 	/**
 	 * Check if the persistant mapping of enabled/disabled state of each available
 	 * provider is missing an entry and add it to the registry in that case.
diff --git a/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php b/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php
new file mode 100644
index 0000000000000000000000000000000000000000..8914295d615ee8dbb95c10b3aae412f5ab54b5aa
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/ALoginSetupController.php
@@ -0,0 +1,34 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\AppFramework\Controller;
+
+/**
+ * @since 17.0.0
+ */
+abstract class ALoginSetupController extends Controller {
+
+}
diff --git a/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php b/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php
new file mode 100644
index 0000000000000000000000000000000000000000..22d5c6d14476cf47cad5660466766d6753754383
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/IActivatableAtLogin.php
@@ -0,0 +1,43 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\IUser;
+
+/**
+ * @since 17.0.0
+ */
+interface IActivatableAtLogin extends IProvider {
+
+	/**
+	 * @param IUser $user
+	 *
+	 * @return ILoginSetupProvider
+	 *
+	 * @since 17.0.0
+	 */
+	public function getLoginSetup(IUser $user): ILoginSetupProvider;
+
+}
diff --git a/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php
new file mode 100644
index 0000000000000000000000000000000000000000..7815f60b66af2b3c26c104ef0dbcdab319024724
--- /dev/null
+++ b/lib/public/Authentication/TwoFactorAuth/ILoginSetupProvider.php
@@ -0,0 +1,41 @@
+<?php
+declare(strict_types=1);
+/**
+ * @copyright Copyright (c) 2019, Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @author Roeland Jago Douma <roeland@famdouma.nl>
+ *
+ * @license GNU AGPL version 3 or any later version
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU Affero General Public License as
+ * published by the Free Software Foundation, either version 3 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Affero General Public License for more details.
+ *
+ * You should have received a copy of the GNU Affero General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace OCP\Authentication\TwoFactorAuth;
+
+use OCP\Template;
+
+/**
+ * @since 17.0.0
+ */
+interface ILoginSetupProvider {
+
+	/**
+	 * @return Template
+	 *
+	 * @since 17.0.0
+	 */
+	public function getBody(): Template;
+
+}
diff --git a/tests/Core/Controller/TwoFactorChallengeControllerTest.php b/tests/Core/Controller/TwoFactorChallengeControllerTest.php
index a405914cc47eddbde8f4853717cd3806f09a4860..73b035a408ccee84d55dfa69c79a4ce1a6950f29 100644
--- a/tests/Core/Controller/TwoFactorChallengeControllerTest.php
+++ b/tests/Core/Controller/TwoFactorChallengeControllerTest.php
@@ -28,6 +28,8 @@ use OC\Core\Controller\TwoFactorChallengeController;
 use OC_Util;
 use OCP\AppFramework\Http\RedirectResponse;
 use OCP\AppFramework\Http\StandaloneTemplateResponse;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
+use OCP\Authentication\TwoFactorAuth\ILoginSetupProvider;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\Authentication\TwoFactorAuth\TwoFactorException;
 use OCP\IRequest;
@@ -86,11 +88,15 @@ class TwoFactorChallengeControllerTest extends TestCase {
 
 	public function testSelectChallenge() {
 		$user = $this->getMockBuilder(IUser::class)->getMock();
-		$p1 = $this->createMock(IProvider::class);
+		$p1 = $this->createMock(IActivatableAtLogin::class);
 		$p1->method('getId')->willReturn('p1');
 		$backupProvider = $this->createMock(IProvider::class);
 		$backupProvider->method('getId')->willReturn('backup_codes');
 		$providerSet = new ProviderSet([$p1, $backupProvider], true);
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($user)
+			->willReturn([$p1]);
 
 		$this->userSession->expects($this->once())
 			->method('getUser')
@@ -108,7 +114,8 @@ class TwoFactorChallengeControllerTest extends TestCase {
 			'backupProvider' => $backupProvider,
 			'redirect_url' => '/some/url',
 			'logout_url' => 'logoutAttribute',
-			], 'guest');
+			'hasSetupProviders' => true,
+		], 'guest');
 
 		$this->assertEquals($expected, $this->controller->selectChallenge('/some/url'));
 	}
@@ -159,7 +166,7 @@ class TwoFactorChallengeControllerTest extends TestCase {
 			'template' => '<html/>',
 			'redirect_url' => '/re/dir/ect/url',
 			'error_message' => null,
-			], 'guest');
+		], 'guest');
 
 		$this->assertEquals($expected, $this->controller->showChallenge('myprovider', '/re/dir/ect/url'));
 	}
@@ -323,4 +330,118 @@ class TwoFactorChallengeControllerTest extends TestCase {
 		$this->assertEquals($expected, $this->controller->solveChallenge('myprovider', 'token', '/url'));
 	}
 
+	public function testSetUpProviders() {
+		$user = $this->createMock(IUser::class);
+		$this->userSession->expects($this->once())
+			->method('getUser')
+			->will($this->returnValue($user));
+		$provider = $this->createMock(IActivatableAtLogin::class);
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($user)
+			->willReturn([
+				$provider,
+			]);
+		$expected = new StandaloneTemplateResponse(
+			'core',
+			'twofactorsetupselection',
+			[
+				'providers' => [
+					$provider,
+				],
+				'logout_url' => 'logoutAttribute',
+			],
+			'guest'
+		);
+
+		$response = $this->controller->setupProviders();
+
+		$this->assertEquals($expected, $response);
+	}
+
+	public function testSetUpInvalidProvider() {
+		$user = $this->createMock(IUser::class);
+		$this->userSession->expects($this->once())
+			->method('getUser')
+			->will($this->returnValue($user));
+		$provider = $this->createMock(IActivatableAtLogin::class);
+		$provider->expects($this->any())
+			->method('getId')
+			->willReturn('prov1');
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($user)
+			->willReturn([
+				$provider,
+			]);
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with('core.TwoFactorChallenge.selectChallenge')
+			->willReturn('2fa/select/page');
+		$expected = new RedirectResponse('2fa/select/page');
+
+		$response = $this->controller->setupProvider('prov2');
+
+		$this->assertEquals($expected, $response);
+	}
+
+	public function testSetUpProvider() {
+		$user = $this->createMock(IUser::class);
+		$this->userSession->expects($this->once())
+			->method('getUser')
+			->will($this->returnValue($user));
+		$provider = $this->createMock(IActivatableAtLogin::class);
+		$provider->expects($this->any())
+			->method('getId')
+			->willReturn('prov1');
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($user)
+			->willReturn([
+				$provider,
+			]);
+		$loginSetup = $this->createMock(ILoginSetupProvider::class);
+		$provider->expects($this->any())
+			->method('getLoginSetup')
+			->with($user)
+			->willReturn($loginSetup);
+		$tmpl = $this->createMock(Template::class);
+		$loginSetup->expects($this->once())
+			->method('getBody')
+			->willReturn($tmpl);
+		$tmpl->expects($this->once())
+			->method('fetchPage')
+			->willReturn('tmpl');
+		$expected = new StandaloneTemplateResponse(
+			'core',
+			'twofactorsetupchallenge',
+			[
+				'provider' => $provider,
+				'logout_url' => 'logoutAttribute',
+				'template' => 'tmpl',
+			],
+			'guest'
+		);
+
+		$response = $this->controller->setupProvider('prov1');
+
+		$this->assertEquals($expected, $response);
+	}
+
+	public function testConfirmProviderSetup() {
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with(
+				'core.TwoFactorChallenge.showChallenge',
+				[
+					'challengeProviderId' => 'totp',
+				])
+			->willReturn('2fa/select/page');
+		$expected = new RedirectResponse('2fa/select/page');
+
+		$response = $this->controller->confirmProviderSetup('totp');
+
+		$this->assertEquals($expected, $response);
+	}
+
 }
diff --git a/tests/Core/Middleware/TwoFactorMiddlewareTest.php b/tests/Core/Middleware/TwoFactorMiddlewareTest.php
index eb72b3e6796842515a9e923125e263ccff9071d4..70566760184d1d51916828b28f0723c443247681 100644
--- a/tests/Core/Middleware/TwoFactorMiddlewareTest.php
+++ b/tests/Core/Middleware/TwoFactorMiddlewareTest.php
@@ -28,20 +28,35 @@ use OC\AppFramework\Http\Request;
 use OC\User\Session;
 use OCP\AppFramework\Controller;
 use OCP\AppFramework\Utility\IControllerMethodReflector;
+use OCP\Authentication\TwoFactorAuth\ALoginSetupController;
 use OCP\IConfig;
+use OCP\IRequest;
 use OCP\ISession;
 use OCP\IURLGenerator;
 use OCP\IUser;
+use OCP\IUserSession;
 use OCP\Security\ISecureRandom;
+use PHPUnit\Framework\MockObject\MockObject;
 use Test\TestCase;
 
 class TwoFactorMiddlewareTest extends TestCase {
 
+	/** @var Manager|MockObject */
 	private $twoFactorManager;
+
+	/** @var IUserSession|MockObject */
 	private $userSession;
+
+	/** @var ISession|MockObject */
 	private $session;
+
+	/** @var IURLGenerator|MockObject */
 	private $urlGenerator;
+
+	/** @var IControllerMethodReflector|MockObject */
 	private $reflector;
+
+	/** @var IRequest|MockObject */
 	private $request;
 
 	/** @var TwoFactorMiddleware */
@@ -102,6 +117,25 @@ class TwoFactorMiddlewareTest extends TestCase {
 		$this->middleware->beforeController($this->controller, 'create');
 	}
 
+	public function testBeforeSetupController() {
+		$user = $this->createMock(IUser::class);
+		$controller = $this->createMock(ALoginSetupController::class);
+		$this->reflector->expects($this->once())
+			->method('hasAnnotation')
+			->with('PublicPage')
+			->willReturn(false);
+		$this->userSession->expects($this->any())
+			->method('getUser')
+			->willReturn($user);
+		$this->twoFactorManager->expects($this->once())
+			->method('needsSecondFactor')
+			->willReturn(true);
+		$this->userSession->expects($this->never())
+			->method('isLoggedIn');
+
+		$this->middleware->beforeController($controller, 'create');
+	}
+
 	public function testBeforeControllerNoTwoFactorCheckNeeded() {
 		$user = $this->createMock(IUser::class);
 
diff --git a/tests/lib/Authentication/Login/TwoFactorCommandTest.php b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
index a5c1c8e352bf121b82fcd48e7cdafff9ed24e4a2..5f91d8125251469c15ef16623027e8b20071e35e 100644
--- a/tests/lib/Authentication/Login/TwoFactorCommandTest.php
+++ b/tests/lib/Authentication/Login/TwoFactorCommandTest.php
@@ -27,7 +27,9 @@ namespace lib\Authentication\Login;
 
 use OC\Authentication\Login\TwoFactorCommand;
 use OC\Authentication\TwoFactorAuth\Manager;
+use OC\Authentication\TwoFactorAuth\MandatoryTwoFactor;
 use OC\Authentication\TwoFactorAuth\ProviderSet;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
 use OCP\Authentication\TwoFactorAuth\IProvider as ITwoFactorAuthProvider;
 use OCP\IURLGenerator;
 use PHPUnit\Framework\MockObject\MockObject;
@@ -37,6 +39,9 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 	/** @var Manager|MockObject */
 	private $twoFactorManager;
 
+	/** @var MandatoryTwoFactor|MockObject */
+	private $mandatoryTwoFactor;
+
 	/** @var IURLGenerator|MockObject */
 	private $urlGenerator;
 
@@ -44,10 +49,12 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 		parent::setUp();
 
 		$this->twoFactorManager = $this->createMock(Manager::class);
+		$this->mandatoryTwoFactor = $this->createMock(MandatoryTwoFactor::class);
 		$this->urlGenerator = $this->createMock(IURLGenerator::class);
 
 		$this->cmd = new TwoFactorCommand(
 			$this->twoFactorManager,
+			$this->mandatoryTwoFactor,
 			$this->urlGenerator
 		);
 	}
@@ -82,6 +89,14 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 			->willReturn(new ProviderSet([
 				$provider,
 			], false));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(false);
 		$provider->expects($this->once())
 			->method('getId')
 			->willReturn('test');
@@ -101,6 +116,47 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 		$this->assertEquals('two/factor/url', $result->getRedirectUrl());
 	}
 
+	public function testProcessMissingProviders() {
+		$data = $this->getLoggedInLoginData();
+		$this->twoFactorManager->expects($this->once())
+			->method('isTwoFactorAuthenticated')
+			->willReturn(true);
+		$this->twoFactorManager->expects($this->once())
+			->method('prepareTwoFactorLogin')
+			->with(
+				$this->user,
+				$data->isRememberLogin()
+			);
+		$provider = $this->createMock(ITwoFactorAuthProvider::class);
+		$provider->expects($this->once())
+			->method('getId')
+			->willReturn('test1');
+		$this->twoFactorManager->expects($this->once())
+			->method('getProviderSet')
+			->willReturn(new ProviderSet([
+				$provider,
+			], true));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(false);
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with(
+				'core.TwoFactorChallenge.selectChallenge'
+			)
+			->willReturn('two/factor/url');
+
+		$result = $this->cmd->process($data);
+
+		$this->assertTrue($result->isSuccess());
+		$this->assertEquals('two/factor/url', $result->getRedirectUrl());
+	}
+
 	public function testProcessTwoActiveProviders() {
 		$data = $this->getLoggedInLoginData();
 		$this->twoFactorManager->expects($this->once())
@@ -126,6 +182,122 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 				$provider1,
 				$provider2,
 			], false));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(false);
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with(
+				'core.TwoFactorChallenge.selectChallenge'
+			)
+			->willReturn('two/factor/url');
+
+		$result = $this->cmd->process($data);
+
+		$this->assertTrue($result->isSuccess());
+		$this->assertEquals('two/factor/url', $result->getRedirectUrl());
+	}
+
+	public function testProcessFailingProviderAndEnforcedButNoSetupProviders() {
+		$data = $this->getLoggedInLoginData();
+		$this->twoFactorManager->expects($this->once())
+			->method('isTwoFactorAuthenticated')
+			->willReturn(true);
+		$this->twoFactorManager->expects($this->once())
+			->method('prepareTwoFactorLogin')
+			->with(
+				$this->user,
+				$data->isRememberLogin()
+			);
+		$this->twoFactorManager->expects($this->once())
+			->method('getProviderSet')
+			->willReturn(new ProviderSet([], true));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(true);
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with(
+				'core.TwoFactorChallenge.selectChallenge'
+			)
+			->willReturn('two/factor/url');
+
+		$result = $this->cmd->process($data);
+
+		$this->assertTrue($result->isSuccess());
+		$this->assertEquals('two/factor/url', $result->getRedirectUrl());
+	}
+
+	public function testProcessFailingProviderAndEnforced() {
+		$data = $this->getLoggedInLoginData();
+		$this->twoFactorManager->expects($this->once())
+			->method('isTwoFactorAuthenticated')
+			->willReturn(true);
+		$this->twoFactorManager->expects($this->once())
+			->method('prepareTwoFactorLogin')
+			->with(
+				$this->user,
+				$data->isRememberLogin()
+			);
+		$provider = $this->createMock(IActivatableAtLogin::class);
+		$this->twoFactorManager->expects($this->once())
+			->method('getProviderSet')
+			->willReturn(new ProviderSet([
+				$provider,
+			], true));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(true);
+		$this->urlGenerator->expects($this->once())
+			->method('linkToRoute')
+			->with(
+				'core.TwoFactorChallenge.selectChallenge'
+			)
+			->willReturn('two/factor/url');
+
+		$result = $this->cmd->process($data);
+
+		$this->assertTrue($result->isSuccess());
+		$this->assertEquals('two/factor/url', $result->getRedirectUrl());
+	}
+
+	public function testProcessNoProvidersButEnforced() {
+		$data = $this->getLoggedInLoginData();
+		$this->twoFactorManager->expects($this->once())
+			->method('isTwoFactorAuthenticated')
+			->willReturn(true);
+		$this->twoFactorManager->expects($this->once())
+			->method('prepareTwoFactorLogin')
+			->with(
+				$this->user,
+				$data->isRememberLogin()
+			);
+		$this->twoFactorManager->expects($this->once())
+			->method('getProviderSet')
+			->willReturn(new ProviderSet([], false));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(true);
 		$this->urlGenerator->expects($this->once())
 			->method('linkToRoute')
 			->with(
@@ -156,6 +328,14 @@ class TwoFactorCommandTest extends ALoginCommandTest {
 			->willReturn(new ProviderSet([
 				$provider,
 			], false));
+		$this->twoFactorManager->expects($this->once())
+			->method('getLoginSetupProviders')
+			->with($this->user)
+			->willReturn([]);
+		$this->mandatoryTwoFactor->expects($this->any())
+			->method('isEnforcedFor')
+			->with($this->user)
+			->willReturn(false);
 		$provider->expects($this->once())
 			->method('getId')
 			->willReturn('test');
diff --git a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
index 0f09691bc1cc1e8e62ff22b6869e14cdb5feb8ce..e836e8d316bb9c8efb87135c055b9c2f0c586252 100644
--- a/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
+++ b/tests/lib/Authentication/TwoFactorAuth/ManagerTest.php
@@ -31,6 +31,7 @@ use OC\Authentication\TwoFactorAuth\ProviderLoader;
 use OCP\Activity\IEvent;
 use OCP\Activity\IManager;
 use OCP\AppFramework\Utility\ITimeFactory;
+use OCP\Authentication\TwoFactorAuth\IActivatableAtLogin;
 use OCP\Authentication\TwoFactorAuth\IProvider;
 use OCP\Authentication\TwoFactorAuth\IRegistry;
 use OCP\IConfig;
@@ -38,6 +39,7 @@ use OCP\ILogger;
 use OCP\ISession;
 use OCP\IUser;
 use PHPUnit\Framework\MockObject\MockObject;
+use function reset;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
 use Test\TestCase;
 
@@ -297,6 +299,23 @@ class ManagerTest extends TestCase {
 		$this->assertNull($provider);
 	}
 
+	public function testGetLoginSetupProviders() {
+		$provider1 = $this->createMock(IProvider::class);
+		$provider2 = $this->createMock(IActivatableAtLogin::class);
+		$this->providerLoader->expects($this->once())
+			->method('getProviders')
+			->with($this->user)
+			->willReturn([
+				$provider1,
+				$provider2,
+			]);
+
+		$providers = $this->manager->getLoginSetupProviders($this->user);
+
+		$this->assertCount(1, $providers);
+		$this->assertSame($provider2, reset($providers));
+	}
+
 	public function testGetProviders() {
 		$this->providerRegistry->expects($this->once())
 			->method('getProviderStates')