diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/AuthManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/AuthManager.kt
index 455bdff772511e233b603a562bd2ae7674aa0d6f..f72d5aac658c7e6b13ba22d1bd52ce2d6e34a548 100644
--- a/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/AuthManager.kt
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/server/AuthManager.kt
@@ -5,19 +5,15 @@ import org.briarproject.mailbox.core.contacts.Contact
 import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.server.MailboxPrincipal.ContactPrincipal
 import org.briarproject.mailbox.core.server.MailboxPrincipal.OwnerPrincipal
-import org.briarproject.mailbox.core.settings.SettingsManager
+import org.briarproject.mailbox.core.setup.SetupManager
 import org.briarproject.mailbox.core.system.RandomIdManager
 import javax.inject.Inject
 import javax.inject.Singleton
 
-// We might want to move this somewhere else later
-internal const val SETTINGS_NAMESPACE_OWNER = "owner"
-internal const val SETTINGS_OWNER_TOKEN = "ownerToken"
-
 @Singleton
 class AuthManager @Inject constructor(
     private val db: Database,
-    private val settingsManager: SettingsManager,
+    private val setupManager: SetupManager,
     private val randomIdManager: RandomIdManager,
 ) {
 
@@ -32,8 +28,7 @@ class AuthManager @Inject constructor(
             if (contact != null) {
                 ContactPrincipal(contact)
             } else {
-                val settings = settingsManager.getSettings(txn, SETTINGS_NAMESPACE_OWNER)
-                if (token == settings[SETTINGS_OWNER_TOKEN]) OwnerPrincipal
+                if (token == setupManager.getOwnerToken(txn)) OwnerPrincipal
                 else null
             }
         }
diff --git a/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt
new file mode 100644
index 0000000000000000000000000000000000000000..14002b3a48905b5d5ab2fe992e69f6905cb95d81
--- /dev/null
+++ b/mailbox-core/src/main/java/org/briarproject/mailbox/core/setup/SetupManager.kt
@@ -0,0 +1,47 @@
+package org.briarproject.mailbox.core.setup
+
+import org.briarproject.mailbox.core.db.DbException
+import org.briarproject.mailbox.core.db.Transaction
+import org.briarproject.mailbox.core.settings.Settings
+import org.briarproject.mailbox.core.settings.SettingsManager
+import org.briarproject.mailbox.core.system.RandomIdManager
+import javax.inject.Inject
+
+private const val SETTINGS_NAMESPACE_OWNER = "owner"
+private const val SETTINGS_SETUP_TOKEN = "setupToken"
+private const val SETTINGS_OWNER_TOKEN = "ownerToken"
+
+class SetupManager @Inject constructor(
+    private val randomIdManager: RandomIdManager,
+    private val settingsManager: SettingsManager,
+) {
+
+    /**
+     * Stores a new single-use setup token and wipes the owner auth token, if one existed.
+     */
+    fun restartSetup() {
+        val settings = Settings()
+        settings[SETTINGS_SETUP_TOKEN] = randomIdManager.getNewRandomId()
+        settings[SETTINGS_OWNER_TOKEN] = "" // we can't remove or null or, so we need to empty it
+        settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE_OWNER)
+    }
+
+    /**
+     * Visible for testing, consider private.
+     */
+    @Throws(DbException::class)
+    internal fun setOwnerToken(token: String) {
+        val settings = Settings()
+        settings[SETTINGS_OWNER_TOKEN] = token
+        settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE_OWNER)
+    }
+
+    @Throws(DbException::class)
+    fun getOwnerToken(txn: Transaction): String? {
+        val settings = settingsManager.getSettings(txn, SETTINGS_NAMESPACE_OWNER)
+        val ownerToken = settings[SETTINGS_OWNER_TOKEN]
+        return if (ownerToken.isNullOrEmpty()) null
+        else ownerToken
+    }
+
+}
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt
index c374303c762f7845193081bf282b218a0dca1830..564531b71e307b705f0864f5c9559cc752fc057a 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/TestComponent.kt
@@ -5,6 +5,7 @@ import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.files.FileManager
 import org.briarproject.mailbox.core.lifecycle.LifecycleManager
 import org.briarproject.mailbox.core.settings.SettingsManager
+import org.briarproject.mailbox.core.setup.SetupManager
 import org.briarproject.mailbox.core.system.RandomIdManager
 import javax.inject.Singleton
 
@@ -18,6 +19,7 @@ interface TestComponent {
     fun injectCoreEagerSingletons(): CoreEagerSingletons
     fun getLifecycleManager(): LifecycleManager
     fun getSettingsManager(): SettingsManager
+    fun getSetupManager(): SetupManager
     fun getFileManager(): FileManager
     fun getDatabase(): Database
     fun getRandomIdManager(): RandomIdManager
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/AuthManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/AuthManagerTest.kt
index 1a3d6f5b0992b952674f1771900e817c070f5935..6eaf19addf139cab6d53819bfee0fc73ba84265c 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/AuthManagerTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/AuthManagerTest.kt
@@ -7,8 +7,7 @@ import org.briarproject.mailbox.core.TestUtils.getNewRandomContact
 import org.briarproject.mailbox.core.TestUtils.getNewRandomId
 import org.briarproject.mailbox.core.db.Database
 import org.briarproject.mailbox.core.server.MailboxPrincipal.OwnerPrincipal
-import org.briarproject.mailbox.core.settings.Settings
-import org.briarproject.mailbox.core.settings.SettingsManager
+import org.briarproject.mailbox.core.setup.SetupManager
 import org.briarproject.mailbox.core.system.InvalidIdException
 import org.briarproject.mailbox.core.system.RandomIdManager
 import org.briarproject.mailbox.core.system.toHex
@@ -21,10 +20,10 @@ import kotlin.test.assertNull
 class AuthManagerTest {
 
     private val db: Database = mockk()
-    private val settingsManager: SettingsManager = mockk()
+    private val setupManager: SetupManager = mockk()
     private val randomIdManager = RandomIdManager()
 
-    private val authManager = AuthManager(db, settingsManager, randomIdManager)
+    private val authManager = AuthManager(db, setupManager, randomIdManager)
 
     private val id = getNewRandomId()
     private val otherId = getNewRandomId()
@@ -49,13 +48,9 @@ class AuthManagerTest {
 
     @Test
     fun `getPrincipal() returns authenticated owner`() {
-        val settings = Settings().apply {
-            put(SETTINGS_OWNER_TOKEN, id)
-        }
-
         everyTransactionWithResult(db, true) { txn ->
             every { db.getContactWithToken(txn, id) } returns null
-            every { settingsManager.getSettings(txn, SETTINGS_NAMESPACE_OWNER) } returns settings
+            every { setupManager.getOwnerToken(txn) } returns id
         }
 
         assertEquals(OwnerPrincipal, authManager.getPrincipal(id))
@@ -63,13 +58,9 @@ class AuthManagerTest {
 
     @Test
     fun `getPrincipal() returns null when unauthenticated`() {
-        val settings = Settings().apply {
-            put(SETTINGS_OWNER_TOKEN, otherId)
-        }
-
         everyTransactionWithResult(db, true) { txn ->
             every { db.getContactWithToken(txn, id) } returns null
-            every { settingsManager.getSettings(txn, SETTINGS_NAMESPACE_OWNER) } returns settings
+            every { setupManager.getOwnerToken(txn) } returns otherId
         }
 
         assertNull(authManager.getPrincipal(id))
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
index d963bd1161b04bc19120967702551c9beb75076b..140ef2e3aed6b908a02b841ba48587f6911bcf9a 100644
--- a/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/server/IntegrationTest.kt
@@ -14,7 +14,6 @@ import org.briarproject.mailbox.core.TestUtils.getNewRandomContact
 import org.briarproject.mailbox.core.TestUtils.getNewRandomId
 import org.briarproject.mailbox.core.contacts.Contact
 import org.briarproject.mailbox.core.server.WebServerManager.Companion.PORT
-import org.briarproject.mailbox.core.settings.Settings
 import org.junit.jupiter.api.AfterAll
 import org.junit.jupiter.api.BeforeAll
 import org.junit.jupiter.api.TestInstance
@@ -58,10 +57,7 @@ abstract class IntegrationTest(private val installJsonFeature: Boolean = true) {
     }
 
     protected fun addOwnerToken() {
-        val settingsManager = testComponent.getSettingsManager()
-        val settings = Settings()
-        settings[SETTINGS_OWNER_TOKEN] = ownerToken
-        settingsManager.mergeSettings(settings, SETTINGS_NAMESPACE_OWNER)
+        testComponent.getSetupManager().setOwnerToken(ownerToken)
     }
 
     protected fun addContact(c: Contact) {
diff --git a/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/SetupManagerTest.kt b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/SetupManagerTest.kt
new file mode 100644
index 0000000000000000000000000000000000000000..88470f41171f36d77efb80976f96e04e2b938941
--- /dev/null
+++ b/mailbox-core/src/test/java/org/briarproject/mailbox/core/setup/SetupManagerTest.kt
@@ -0,0 +1,36 @@
+package org.briarproject.mailbox.core.setup
+
+import org.briarproject.mailbox.core.server.IntegrationTest
+import org.junit.jupiter.api.Test
+import kotlin.test.assertEquals
+import kotlin.test.assertNull
+
+class SetupManagerTest : IntegrationTest() {
+
+    private val db by lazy { testComponent.getDatabase() }
+    private val setupManager by lazy { testComponent.getSetupManager() }
+
+    @Test
+    fun `restarting setup wipes owner token`() {
+        // initially, there's no owner token
+        val initialOwnerToken = db.transactionWithResult(true) { txn ->
+            setupManager.getOwnerToken(txn)
+        }
+        assertNull(initialOwnerToken)
+
+        // setting an owner token stores it in DB
+        setupManager.setOwnerToken(ownerToken)
+        val firstOwnerToken = db.transactionWithResult(true) { txn ->
+            setupManager.getOwnerToken(txn)
+        }
+        assertEquals(ownerToken, firstOwnerToken)
+
+        // restarting setup wipes owner token
+        setupManager.restartSetup()
+        val wipedOwnerToken = db.transactionWithResult(true) { txn ->
+            setupManager.getOwnerToken(txn)
+        }
+        assertNull(wipedOwnerToken)
+    }
+
+}
\ No newline at end of file