diff --git a/app/Auth/Access/LoginService.php b/app/Auth/Access/LoginService.php
index cc0cb06f3..f6ea7517f 100644
--- a/app/Auth/Access/LoginService.php
+++ b/app/Auth/Access/LoginService.php
@@ -10,6 +10,7 @@ use BookStack\Facades\Activity;
 use BookStack\Facades\Theme;
 use BookStack\Theming\ThemeEvents;
 use Exception;
+use phpDocumentor\Reflection\DocBlock\Tags\Method;
 
 class LoginService
 {
@@ -33,7 +34,7 @@ class LoginService
     public function login(User $user, string $method): void
     {
         if ($this->awaitingEmailConfirmation($user) || $this->needsMfaVerification($user)) {
-            $this->setLastLoginAttemptedForUser($user);
+            $this->setLastLoginAttemptedForUser($user, $method);
             throw new StoppedAuthenticationException($user, $this);
             // TODO - Does 'remember' still work? Probably not right now.
 
@@ -41,12 +42,6 @@ class LoginService
 
             // Old MFA middleware todos:
 
-            // TODO - Need to redirect to setup if not configured AND ONLY IF NO OPTIONS CONFIGURED
-            //    Might need to change up such routes to start with /configure/ for such identification.
-            //    (Can't allow access to those if already configured)
-            //    Or, More likely, Need to add defence to those to prevent access unless
-            //    logged in or during partial auth.
-
             // TODO - Handle email confirmation handling
             //  Left BookStack\Http\Middleware\Authenticate@emailConfirmationErrorResponse in which needs
             //  be removed as an example of old behaviour.
@@ -70,13 +65,13 @@ class LoginService
      * Reattempt a system login after a previous stopped attempt.
      * @throws Exception
      */
-    public function reattemptLoginFor(User $user, string $method)
+    public function reattemptLoginFor(User $user)
     {
         if ($user->id !== ($this->getLastLoginAttemptUser()->id ?? null)) {
             throw new Exception('Login reattempt user does align with current session state');
         }
 
-        $this->login($user, $method);
+        $this->login($user, $this->getLastLoginAttemptMethod());
     }
 
     /**
@@ -86,12 +81,38 @@ class LoginService
      */
     public function getLastLoginAttemptUser(): ?User
     {
-        $id = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
-        if (!$id) {
-            return null;
+        $id = $this->getLastLoginAttemptDetails()['user_id'];
+        return User::query()->where('id', '=', $id)->first();
+    }
+
+    /**
+     * Get the method for the last login attempt.
+     */
+    protected function getLastLoginAttemptMethod(): ?string
+    {
+        return $this->getLastLoginAttemptDetails()['method'];
+    }
+
+    /**
+     * Get the details of the last login attempt.
+     * Checks upon a ttl of about 1 hour since that last attempted login.
+     * @return array{user_id: ?string, method: ?string}
+     */
+    protected function getLastLoginAttemptDetails(): array
+    {
+        $value = session()->get(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY);
+        if (!$value) {
+            return ['user_id' => null, 'method' => null];
         }
 
-        return User::query()->where('id', '=', $id)->first();
+        [$id, $method, $time] = explode(':', $value);
+        $hourAgo = time() - (60*60);
+        if ($time < $hourAgo) {
+            $this->clearLastLoginAttempted();
+            return ['user_id' => null, 'method' => null];
+        }
+
+        return ['user_id' => $id, 'method' => $method];
     }
 
     /**
@@ -99,9 +120,12 @@ class LoginService
      * Must be only used when credentials are correct and a login could be
      * achieved but a secondary factor has stopped the login.
      */
-    protected function setLastLoginAttemptedForUser(User $user)
+    protected function setLastLoginAttemptedForUser(User $user, string $method)
     {
-        session()->put(self::LAST_LOGIN_ATTEMPTED_SESSION_KEY, $user->id);
+        session()->put(
+            self::LAST_LOGIN_ATTEMPTED_SESSION_KEY,
+            implode(':', [$user->id, $method, time()])
+        );
     }
 
     /**
diff --git a/app/Auth/Access/Mfa/MfaSession.php b/app/Auth/Access/Mfa/MfaSession.php
index dabd568f7..8821dbb9d 100644
--- a/app/Auth/Access/Mfa/MfaSession.php
+++ b/app/Auth/Access/Mfa/MfaSession.php
@@ -15,6 +15,15 @@ class MfaSession
         return $user->mfaValues()->exists() || $this->userRoleEnforcesMfa($user);
     }
 
+    /**
+     * Check if the given user is pending MFA setup.
+     * (MFA required but not yet configured).
+     */
+    public function isPendingMfaSetup(User $user): bool
+    {
+        return $this->isRequiredForUser($user) && !$user->mfaValues()->exists();
+    }
+
     /**
      * Check if a role of the given user enforces MFA.
      */
diff --git a/app/Http/Controllers/Auth/MfaBackupCodesController.php b/app/Http/Controllers/Auth/MfaBackupCodesController.php
index 1353d4562..41c161d7c 100644
--- a/app/Http/Controllers/Auth/MfaBackupCodesController.php
+++ b/app/Http/Controllers/Auth/MfaBackupCodesController.php
@@ -78,7 +78,7 @@ class MfaBackupCodesController extends Controller
         MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, $updatedCodes);
 
         $mfaSession->markVerifiedForUser($user);
-        $loginService->reattemptLoginFor($user, 'mfa-backup_codes');
+        $loginService->reattemptLoginFor($user);
 
         if ($codeService->countCodesInSet($updatedCodes) < 5) {
             $this->showWarningNotification('You have less than 5 backup codes remaining, Please generate and store a new set before you run out of codes to prevent being locked out of your account.');
diff --git a/app/Http/Controllers/Auth/MfaTotpController.php b/app/Http/Controllers/Auth/MfaTotpController.php
index dd1651970..a1701c4ce 100644
--- a/app/Http/Controllers/Auth/MfaTotpController.php
+++ b/app/Http/Controllers/Auth/MfaTotpController.php
@@ -82,7 +82,7 @@ class MfaTotpController extends Controller
         ]);
 
         $mfaSession->markVerifiedForUser($user);
-        $loginService->reattemptLoginFor($user, 'mfa-totp');
+        $loginService->reattemptLoginFor($user);
 
         return redirect()->intended();
     }
diff --git a/app/Http/Kernel.php b/app/Http/Kernel.php
index 4f9bfc1e6..d1f5de917 100644
--- a/app/Http/Kernel.php
+++ b/app/Http/Kernel.php
@@ -53,5 +53,6 @@ class Kernel extends HttpKernel
         'throttle'   => \Illuminate\Routing\Middleware\ThrottleRequests::class,
         'perm'       => \BookStack\Http\Middleware\PermissionMiddleware::class,
         'guard'      => \BookStack\Http\Middleware\CheckGuard::class,
+        'mfa-setup'  => \BookStack\Http\Middleware\AuthenticatedOrPendingMfa::class,
     ];
 }
diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php
index c687c75a2..b9b5545f2 100644
--- a/app/Http/Middleware/Authenticate.php
+++ b/app/Http/Middleware/Authenticate.php
@@ -15,9 +15,8 @@ class Authenticate
         if (!hasAppAccess()) {
             if ($request->ajax()) {
                 return response('Unauthorized.', 401);
-            } else {
-                return redirect()->guest(url('/login'));
             }
+            return redirect()->guest(url('/login'));
         }
 
         return $next($request);
diff --git a/app/Http/Middleware/AuthenticatedOrPendingMfa.php b/app/Http/Middleware/AuthenticatedOrPendingMfa.php
new file mode 100644
index 000000000..2d68a2a57
--- /dev/null
+++ b/app/Http/Middleware/AuthenticatedOrPendingMfa.php
@@ -0,0 +1,41 @@
+<?php
+
+namespace BookStack\Http\Middleware;
+
+use BookStack\Auth\Access\LoginService;
+use BookStack\Auth\Access\Mfa\MfaSession;
+use Closure;
+
+class AuthenticatedOrPendingMfa
+{
+
+    protected $loginService;
+    protected $mfaSession;
+
+    public function __construct(LoginService $loginService, MfaSession $mfaSession)
+    {
+        $this->loginService = $loginService;
+        $this->mfaSession = $mfaSession;
+    }
+
+
+    /**
+     * Handle an incoming request.
+     *
+     * @param  \Illuminate\Http\Request  $request
+     * @param  \Closure  $next
+     * @return mixed
+     */
+    public function handle($request, Closure $next)
+    {
+        $user = auth()->user();
+        $loggedIn = $user !== null;
+        $lastAttemptUser = $this->loginService->getLastLoginAttemptUser();
+
+        if ($loggedIn || ($lastAttemptUser && $this->mfaSession->isPendingMfaSetup($lastAttemptUser))) {
+            return $next($request);
+        }
+
+        return redirect()->guest(url('/login'));
+    }
+}
diff --git a/resources/views/mfa/backup-codes-generate.blade.php b/resources/views/mfa/backup-codes-generate.blade.php
index 8b437846e..6a491fc07 100644
--- a/resources/views/mfa/backup-codes-generate.blade.php
+++ b/resources/views/mfa/backup-codes-generate.blade.php
@@ -27,7 +27,7 @@
                 Each code can only be used once
             </p>
 
-            <form action="{{ url('/mfa/backup-codes-confirm') }}" method="POST">
+            <form action="{{ url('/mfa/backup_codes/confirm') }}" method="POST">
                 {{ csrf_field() }}
                 <div class="mt-s text-right">
                     <a href="{{ url('/mfa/setup') }}" class="button outline">{{ trans('common.cancel') }}</a>
diff --git a/resources/views/mfa/setup.blade.php b/resources/views/mfa/setup.blade.php
index 2ec8d0f77..f670541bf 100644
--- a/resources/views/mfa/setup.blade.php
+++ b/resources/views/mfa/setup.blade.php
@@ -25,7 +25,7 @@
                                 @icon('check-circle')
                                 Already configured
                             </div>
-                            <a href="{{ url('/mfa/totp-generate') }}" class="button outline small">Reconfigure</a>
+                            <a href="{{ url('/mfa/totp/generate') }}" class="button outline small">Reconfigure</a>
                             <div component="dropdown" class="inline relative">
                                 <button type="button" refs="dropdown@toggle" class="button outline small">Remove</button>
                                 <div refs="dropdown@menu" class="dropdown-menu">
@@ -38,7 +38,7 @@
                                 </div>
                             </div>
                         @else
-                            <a href="{{ url('/mfa/totp-generate') }}" class="button outline">Setup</a>
+                            <a href="{{ url('/mfa/totp/generate') }}" class="button outline">Setup</a>
                         @endif
                     </div>
                 </div>
@@ -57,7 +57,7 @@
                                 @icon('check-circle')
                                 Already configured
                             </div>
-                            <a href="{{ url('/mfa/backup-codes-generate') }}" class="button outline small">Reconfigure</a>
+                            <a href="{{ url('/mfa/backup_codes/generate') }}" class="button outline small">Reconfigure</a>
                             <div component="dropdown" class="inline relative">
                                 <button type="button" refs="dropdown@toggle" class="button outline small">Remove</button>
                                 <div refs="dropdown@menu" class="dropdown-menu">
@@ -70,7 +70,7 @@
                                 </div>
                             </div>
                         @else
-                            <a href="{{ url('/mfa/backup-codes-generate') }}" class="button outline">Setup</a>
+                            <a href="{{ url('/mfa/backup_codes/generate') }}" class="button outline">Setup</a>
                         @endif
                     </div>
                 </div>
diff --git a/resources/views/mfa/totp-generate.blade.php b/resources/views/mfa/totp-generate.blade.php
index c1e7547d5..2ed2677d1 100644
--- a/resources/views/mfa/totp-generate.blade.php
+++ b/resources/views/mfa/totp-generate.blade.php
@@ -24,7 +24,7 @@
                 Verify that all is working by entering a code, generated within your
                 authentication app, in the input box below:
             </p>
-            <form action="{{ url('/mfa/totp-confirm') }}" method="POST">
+            <form action="{{ url('/mfa/totp/confirm') }}" method="POST">
                 {{ csrf_field() }}
                 <input type="text"
                        name="code"
diff --git a/resources/views/mfa/verify/backup_codes.blade.php b/resources/views/mfa/verify/backup_codes.blade.php
index abfb8a9d0..7179696cb 100644
--- a/resources/views/mfa/verify/backup_codes.blade.php
+++ b/resources/views/mfa/verify/backup_codes.blade.php
@@ -4,7 +4,7 @@
     Enter one of your remaining backup codes below:
 </p>
 
-<form action="{{ url('/mfa/verify/backup_codes') }}" method="post">
+<form action="{{ url('/mfa/backup_codes/verify') }}" method="post">
     {{ csrf_field() }}
     <input type="text"
            name="code"
diff --git a/resources/views/mfa/verify/totp.blade.php b/resources/views/mfa/verify/totp.blade.php
index 55784dc8f..fffd30352 100644
--- a/resources/views/mfa/verify/totp.blade.php
+++ b/resources/views/mfa/verify/totp.blade.php
@@ -4,7 +4,7 @@
     Enter the code, generated using your mobile app, below:
 </p>
 
-<form action="{{ url('/mfa/verify/totp') }}" method="post">
+<form action="{{ url('/mfa/totp/verify') }}" method="post">
     {{ csrf_field() }}
     <input type="text"
            name="code"
diff --git a/routes/web.php b/routes/web.php
index eadbca5e8..b6590429c 100644
--- a/routes/web.php
+++ b/routes/web.php
@@ -224,26 +224,27 @@ Route::group(['middleware' => 'auth'], function () {
         Route::put('/roles/{id}', 'RoleController@update');
     });
 
-    // MFA (Auth Mandatory)
-    Route::delete('/mfa/remove/{method}', 'Auth\MfaController@remove');
 });
 
-// MFA (Auth Optional)
-Route::get('/mfa/setup', 'Auth\MfaController@setup');
-Route::get('/mfa/totp-generate', 'Auth\MfaTotpController@generate');
-Route::post('/mfa/totp-confirm', 'Auth\MfaTotpController@confirm');
-Route::get('/mfa/backup-codes-generate', 'Auth\MfaBackupCodesController@generate');
-Route::post('/mfa/backup-codes-confirm', 'Auth\MfaBackupCodesController@confirm');
-Route::get('/mfa/verify', 'Auth\MfaController@verify');
-Route::post('/mfa/verify/totp', 'Auth\MfaTotpController@verify');
-Route::post('/mfa/verify/backup_codes', 'Auth\MfaBackupCodesController@verify');
+// MFA routes
+Route::group(['middleware' => 'mfa-setup'], function() {
+    Route::get('/mfa/setup', 'Auth\MfaController@setup');
+    Route::get('/mfa/totp/generate', 'Auth\MfaTotpController@generate');
+    Route::post('/mfa/totp/confirm', 'Auth\MfaTotpController@confirm');
+    Route::get('/mfa/backup_codes/generate', 'Auth\MfaBackupCodesController@generate');
+    Route::post('/mfa/backup_codes/confirm', 'Auth\MfaBackupCodesController@confirm');
+});
+Route::group(['middleware' => 'guest'], function() {
+    Route::get('/mfa/verify', 'Auth\MfaController@verify');
+    Route::post('/mfa/totp/verify', 'Auth\MfaTotpController@verify');
+    Route::post('/mfa/backup_codes/verify', 'Auth\MfaBackupCodesController@verify');
+});
+Route::delete('/mfa/remove/{method}', 'Auth\MfaController@remove')->middleware('auth');
 
 // Social auth routes
 Route::get('/login/service/{socialDriver}', 'Auth\SocialController@login');
 Route::get('/login/service/{socialDriver}/callback', 'Auth\SocialController@callback');
-Route::group(['middleware' => 'auth'], function () {
-    Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach');
-});
+Route::post('/login/service/{socialDriver}/detach', 'Auth\SocialController@detach')->middleware('auth');
 Route::get('/register/service/{socialDriver}', 'Auth\SocialController@register');
 
 // Login/Logout routes
diff --git a/tests/Auth/MfaConfigurationTest.php b/tests/Auth/MfaConfigurationTest.php
index a8ca815fb..63a5fa4dd 100644
--- a/tests/Auth/MfaConfigurationTest.php
+++ b/tests/Auth/MfaConfigurationTest.php
@@ -18,21 +18,21 @@ class MfaConfigurationTest extends TestCase
 
         // Setup page state
         $resp = $this->actingAs($editor)->get('/mfa/setup');
-        $resp->assertElementContains('a[href$="/mfa/totp-generate"]', 'Setup');
+        $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Setup');
 
         // Generate page access
-        $resp = $this->get('/mfa/totp-generate');
+        $resp = $this->get('/mfa/totp/generate');
         $resp->assertSee('Mobile App Setup');
         $resp->assertSee('Verify Setup');
-        $resp->assertElementExists('form[action$="/mfa/totp-confirm"] button');
+        $resp->assertElementExists('form[action$="/mfa/totp/confirm"] button');
         $this->assertSessionHas('mfa-setup-totp-secret');
         $svg = $resp->getElementHtml('#main-content .card svg');
 
         // Validation error, code should remain the same
-        $resp = $this->post('/mfa/totp-confirm', [
+        $resp = $this->post('/mfa/totp/confirm', [
             'code' => 'abc123',
         ]);
-        $resp->assertRedirect('/mfa/totp-generate');
+        $resp->assertRedirect('/mfa/totp/generate');
         $resp = $this->followRedirects($resp);
         $resp->assertSee('The provided code is not valid or has expired.');
         $revisitSvg = $resp->getElementHtml('#main-content .card svg');
@@ -42,7 +42,7 @@ class MfaConfigurationTest extends TestCase
         $google2fa = new Google2FA();
         $secret = decrypt(session()->get('mfa-setup-totp-secret'));
         $otp = $google2fa->getCurrentOtp($secret);
-        $resp = $this->post('/mfa/totp-confirm', [
+        $resp = $this->post('/mfa/totp/confirm', [
             'code' => $otp,
         ]);
         $resp->assertRedirect('/mfa/setup');
@@ -50,7 +50,7 @@ class MfaConfigurationTest extends TestCase
         // Confirmation of setup
         $resp = $this->followRedirects($resp);
         $resp->assertSee('Multi-factor method successfully configured');
-        $resp->assertElementContains('a[href$="/mfa/totp-generate"]', 'Reconfigure');
+        $resp->assertElementContains('a[href$="/mfa/totp/generate"]', 'Reconfigure');
 
         $this->assertDatabaseHas('mfa_values', [
             'user_id' => $editor->id,
@@ -69,12 +69,12 @@ class MfaConfigurationTest extends TestCase
 
         // Setup page state
         $resp = $this->actingAs($editor)->get('/mfa/setup');
-        $resp->assertElementContains('a[href$="/mfa/backup-codes-generate"]', 'Setup');
+        $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Setup');
 
         // Generate page access
-        $resp = $this->get('/mfa/backup-codes-generate');
+        $resp = $this->get('/mfa/backup_codes/generate');
         $resp->assertSee('Backup Codes');
-        $resp->assertElementContains('form[action$="/mfa/backup-codes-confirm"]', 'Confirm and Enable');
+        $resp->assertElementContains('form[action$="/mfa/backup_codes/confirm"]', 'Confirm and Enable');
         $this->assertSessionHas('mfa-setup-backup-codes');
         $codes = decrypt(session()->get('mfa-setup-backup-codes'));
         // Check code format
@@ -84,13 +84,13 @@ class MfaConfigurationTest extends TestCase
         $resp->assertSee(base64_encode(implode("\n\n", $codes)));
 
         // Confirm submit
-        $resp = $this->post('/mfa/backup-codes-confirm');
+        $resp = $this->post('/mfa/backup_codes/confirm');
         $resp->assertRedirect('/mfa/setup');
 
         // Confirmation of setup
         $resp = $this->followRedirects($resp);
         $resp->assertSee('Multi-factor method successfully configured');
-        $resp->assertElementContains('a[href$="/mfa/backup-codes-generate"]', 'Reconfigure');
+        $resp->assertElementContains('a[href$="/mfa/backup_codes/generate"]', 'Reconfigure');
 
         $this->assertDatabaseHas('mfa_values', [
             'user_id' => $editor->id,
@@ -104,7 +104,7 @@ class MfaConfigurationTest extends TestCase
 
     public function test_backup_codes_cannot_be_confirmed_if_not_previously_generated()
     {
-        $resp = $this->asEditor()->post('/mfa/backup-codes-confirm');
+        $resp = $this->asEditor()->post('/mfa/backup_codes/confirm');
         $resp->assertStatus(500);
     }
 
diff --git a/tests/Auth/MfaVerificationTest.php b/tests/Auth/MfaVerificationTest.php
index 3f272cffb..d007fa490 100644
--- a/tests/Auth/MfaVerificationTest.php
+++ b/tests/Auth/MfaVerificationTest.php
@@ -2,9 +2,12 @@
 
 namespace Tests\Auth;
 
+use BookStack\Auth\Access\LoginService;
 use BookStack\Auth\Access\Mfa\MfaValue;
 use BookStack\Auth\Access\Mfa\TotpService;
+use BookStack\Auth\Role;
 use BookStack\Auth\User;
+use BookStack\Exceptions\StoppedAuthenticationException;
 use Illuminate\Support\Facades\Hash;
 use PragmaRX\Google2FA\Google2FA;
 use Tests\TestCase;
@@ -20,10 +23,10 @@ class MfaVerificationTest extends TestCase
         $resp = $this->get('/mfa/verify');
         $resp->assertSee('Verify Access');
         $resp->assertSee('Enter the code, generated using your mobile app, below:');
-        $resp->assertElementExists('form[action$="/mfa/verify/totp"] input[name="code"]');
+        $resp->assertElementExists('form[action$="/mfa/totp/verify"] input[name="code"]');
 
         $google2fa = new Google2FA();
-        $resp = $this->post('/mfa/verify/totp', [
+        $resp = $this->post('/mfa/totp/verify', [
             'code' => $google2fa->getCurrentOtp($secret),
         ]);
         $resp->assertRedirect('/');
@@ -35,7 +38,7 @@ class MfaVerificationTest extends TestCase
         [$user, $secret, $loginResp] = $this->startTotpLogin();
 
         $resp = $this->get('/mfa/verify');
-        $resp = $this->post('/mfa/verify/totp', [
+        $resp = $this->post('/mfa/totp/verify', [
             'code' => '',
         ]);
         $resp->assertRedirect('/mfa/verify');
@@ -44,7 +47,7 @@ class MfaVerificationTest extends TestCase
         $resp->assertSeeText('The code field is required.');
         $this->assertNull(auth()->user());
 
-        $resp = $this->post('/mfa/verify/totp', [
+        $resp = $this->post('/mfa/totp/verify', [
             'code' => '123321',
         ]);
         $resp->assertRedirect('/mfa/verify');
@@ -63,9 +66,9 @@ class MfaVerificationTest extends TestCase
         $resp->assertSee('Verify Access');
         $resp->assertSee('Backup Code');
         $resp->assertSee('Enter one of your remaining backup codes below:');
-        $resp->assertElementExists('form[action$="/mfa/verify/backup_codes"] input[name="code"]');
+        $resp->assertElementExists('form[action$="/mfa/backup_codes/verify"] input[name="code"]');
 
-        $resp = $this->post('/mfa/verify/backup_codes', [
+        $resp = $this->post('/mfa/backup_codes/verify', [
             'code' => $codes[1],
         ]);
 
@@ -82,7 +85,7 @@ class MfaVerificationTest extends TestCase
         [$user, $codes, $loginResp] = $this->startBackupCodeLogin();
 
         $resp = $this->get('/mfa/verify');
-        $resp = $this->post('/mfa/verify/backup_codes', [
+        $resp = $this->post('/mfa/backup_codes/verify', [
             'code' => '',
         ]);
         $resp->assertRedirect('/mfa/verify');
@@ -91,7 +94,7 @@ class MfaVerificationTest extends TestCase
         $resp->assertSeeText('The code field is required.');
         $this->assertNull(auth()->user());
 
-        $resp = $this->post('/mfa/verify/backup_codes', [
+        $resp = $this->post('/mfa/backup_codes/verify', [
             'code' => 'ab123-ab456',
         ]);
         $resp->assertRedirect('/mfa/verify');
@@ -105,7 +108,7 @@ class MfaVerificationTest extends TestCase
     {
         [$user, $codes, $loginResp] = $this->startBackupCodeLogin();
 
-        $this->post('/mfa/verify/backup_codes', [
+        $this->post('/mfa/backup_codes/verify', [
             'code' => $codes[0],
         ]);
         $this->assertNotNull(auth()->user());
@@ -114,7 +117,7 @@ class MfaVerificationTest extends TestCase
 
         $this->post('/login', ['email' => $user->email, 'password' => 'password']);
         $this->get('/mfa/verify');
-        $resp = $this->post('/mfa/verify/backup_codes', [
+        $resp = $this->post('/mfa/backup_codes/verify', [
             'code' => $codes[0],
         ]);
         $resp->assertRedirect('/mfa/verify');
@@ -128,7 +131,7 @@ class MfaVerificationTest extends TestCase
     {
         [$user, $codes, $loginResp] = $this->startBackupCodeLogin(['abc12-def45', 'abc12-def46']);
 
-        $resp = $this->post('/mfa/verify/backup_codes', [
+        $resp = $this->post('/mfa/backup_codes/verify', [
             'code' => $codes[0],
         ]);
         $resp = $this->followRedirects($resp);
@@ -151,16 +154,88 @@ class MfaVerificationTest extends TestCase
         ]);
 
         // Totp shown by default
-        $mfaView->assertElementExists('form[action$="/mfa/verify/totp"] input[name="code"]');
+        $mfaView->assertElementExists('form[action$="/mfa/totp/verify"] input[name="code"]');
         $mfaView->assertElementContains('a[href$="/mfa/verify?method=backup_codes"]', 'Verify using a backup code');
 
         // Ensure can view backup_codes view
         $resp = $this->get('/mfa/verify?method=backup_codes');
-        $resp->assertElementExists('form[action$="/mfa/verify/backup_codes"] input[name="code"]');
+        $resp->assertElementExists('form[action$="/mfa/backup_codes/verify"] input[name="code"]');
         $resp->assertElementContains('a[href$="/mfa/verify?method=totp"]', 'Verify using a mobile app');
     }
 
-    //    TODO !! - Test no-existing MFA
+    public function test_mfa_required_with_no_methods_leads_to_setup()
+    {
+        $user = $this->getEditor();
+        $user->password = Hash::make('password');
+        $user->save();
+        /** @var Role $role */
+        $role = $user->roles->first();
+        $role->mfa_enforced = true;
+        $role->save();
+
+        $this->assertDatabaseMissing('mfa_values', [
+            'user_id' => $user->id,
+        ]);
+
+        /** @var TestResponse $resp */
+        $resp = $this->followingRedirects()->post('/login', [
+            'email' => $user->email,
+            'password' => 'password',
+        ]);
+
+        $resp->assertSeeText('No Methods Configured');
+        $resp->assertElementContains('a[href$="/mfa/setup"]', 'Configure');
+
+        $this->get('/mfa/backup_codes/generate');
+        $this->followingRedirects()->post('/mfa/backup_codes/confirm');
+        $this->assertDatabaseHas('mfa_values', [
+            'user_id' => $user->id,
+        ]);
+
+        $resp = $this->followingRedirects()->post('/login', [
+            'email' => $user->email,
+            'password' => 'password',
+        ]);
+        $resp->assertSeeText('Enter one of your remaining backup codes below:');
+    }
+
+    public function test_mfa_setup_route_access()
+    {
+        $routes = [
+            ['get', '/mfa/setup'],
+            ['get', '/mfa/totp/generate'],
+            ['post', '/mfa/totp/confirm'],
+            ['get', '/mfa/backup_codes/generate'],
+            ['post', '/mfa/backup_codes/confirm'],
+        ];
+
+        // Non-auth access
+        foreach ($routes as [$method, $path]) {
+            $resp = $this->call($method, $path);
+            $resp->assertRedirect('/login');
+        }
+
+        // Attempted login user, who has configured mfa, access
+        // Sets up user that has MFA required after attempted login.
+        $loginService = $this->app->make(LoginService::class);
+        $user = $this->getEditor();
+        /** @var Role $role */
+        $role = $user->roles->first();
+        $role->mfa_enforced = true;
+        $role->save();
+        try {
+            $loginService->login($user, 'testing');
+        } catch (StoppedAuthenticationException $e) {
+        }
+        $this->assertNotNull($loginService->getLastLoginAttemptUser());
+
+        MfaValue::upsertWithValue($user, MfaValue::METHOD_BACKUP_CODES, '[]');
+        foreach ($routes as [$method, $path]) {
+            $resp = $this->call($method, $path);
+            $resp->assertRedirect('/login');
+        }
+
+    }
 
     /**
      * @return Array<User, string, TestResponse>