diff --git a/mod/settings.php b/mod/settings.php
index 259981541b..1935c17091 100644
--- a/mod/settings.php
+++ b/mod/settings.php
@@ -157,31 +157,6 @@ function settings_content(App $a)
 		return '';
 	}
 
-	if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'oauth')) {
-		if ((DI::args()->getArgc() > 3) && (DI::args()->getArgv()[2] === 'delete')) {
-			BaseModule::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth', 't');
-
-			DBA::delete('application-token', ['application-id' => DI::args()->getArgv()[3], 'uid' => DI::userSession()->getLocalUserId()]);
-			DI::baseUrl()->redirect('settings/oauth/', true);
-			return '';
-		}
-
-		$applications = DBA::selectToArray('application-view', ['id', 'uid', 'name', 'website', 'scopes', 'created_at'], ['uid' => DI::userSession()->getLocalUserId()]);
-
-		$tpl = Renderer::getMarkupTemplate('settings/oauth.tpl');
-		$o .= Renderer::replaceMacros($tpl, [
-			'$form_security_token' => BaseModule::getFormSecurityToken("settings_oauth"),
-			'$baseurl'             => DI::baseUrl()->get(true),
-			'$title'               => DI::l10n()->t('Connected Apps'),
-			'$name'                => DI::l10n()->t('Name'),
-			'$website'             => DI::l10n()->t('Home Page'),
-			'$created_at'          => DI::l10n()->t('Created'),
-			'$delete'              => DI::l10n()->t('Remove authorization'),
-			'$apps'                => $applications,
-		]);
-		return $o;
-	}
-
 	if ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'addon')) {
 		$addon_settings_forms = [];
 		foreach (DI::dba()->selectToArray('hook', ['file', 'function'], ['hook' => 'addon_settings']) as $hook) {
diff --git a/src/Module/BaseSettings.php b/src/Module/BaseSettings.php
index 013783c0ab..89dc8d8673 100644
--- a/src/Module/BaseSettings.php
+++ b/src/Module/BaseSettings.php
@@ -21,116 +21,156 @@
 
 namespace Friendica\Module;
 
+use Friendica\App;
 use Friendica\BaseModule;
 use Friendica\Content\Feature;
+use Friendica\Content\Nav;
+use Friendica\Core\L10n;
 use Friendica\Core\Renderer;
-use Friendica\DI;
+use Friendica\Core\Session\Capability\IHandleUserSessions;
+use Friendica\Core\System;
+use Friendica\Module\Security\Login;
+use Friendica\Network\HTTPException\ForbiddenException;
+use Friendica\Util\Profiler;
+use Psr\Log\LoggerInterface;
 
 class BaseSettings extends BaseModule
 {
-	public static function createAside()
+	/** @var App\Page */
+	protected $page;
+	/** @var IHandleUserSessions */
+	protected $session;
+
+	public function __construct(IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
+	{
+		parent::__construct($l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
+
+		$this->page    = $page;
+		$this->session = $session;
+
+		if ($this->session->getSubManagedUserId()) {
+			throw new ForbiddenException($this->t('Permission denied.'));
+		}
+	}
+
+	protected function content(array $request = []): string
+	{
+		Nav::setSelected('settings');
+
+		if (!$this->session->getLocalUserId()) {
+			$this->session->set('return_path', $this->args->getCommand());
+			$this->baseUrl->redirect('login');
+		}
+
+		$this->createAside();
+
+		return '';
+	}
+
+	public function createAside()
 	{
 		$tpl = Renderer::getMarkupTemplate('settings/head.tpl');
-		DI::page()['htmlhead'] .= Renderer::replaceMacros($tpl, [
-			'$ispublic' => DI::l10n()->t('everybody')
+		$this->page['htmlhead'] .= Renderer::replaceMacros($tpl, [
+			'$ispublic' => $this->t('everybody')
 		]);
 
 		$tabs = [];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Account'),
-			'url' => 'settings',
-			'selected' => ((DI::args()->getArgc() == 1) && (DI::args()->getArgv() === 'settings') ? 'active' : ''),
+			'label'     => $this->t('Account'),
+			'url'       => 'settings',
+			'selected'  => static::class == Settings\Account::class ? 'active' : '',
 			'accesskey' => 'o',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Two-factor authentication'),
-			'url' => 'settings/2fa',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === '2fa') ? 'active' : ''),
+			'label'     => $this->t('Two-factor authentication'),
+			'url'       => 'settings/2fa',
+			'selected'  => in_array(static::class, [
+				Settings\TwoFactor\AppSpecific::class,
+				Settings\TwoFactor\Index::class,
+				Settings\TwoFactor\Recovery::class,
+				Settings\TwoFactor\Trusted::class,
+				Settings\TwoFactor\Verify::class
+			]) ? 'active' : '',
 			'accesskey' => '2',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Profile'),
-			'url' => 'settings/profile',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'profile') ? 'active' : ''),
+			'label'     => $this->t('Profile'),
+			'url'       => 'settings/profile',
+			'selected'  => in_array(static::class, [
+				Settings\Profile\Index::class,
+				Settings\Profile\Photo\Crop::class,
+				Settings\Profile\Photo\Index::class,
+			]) ? 'active' : '',
 			'accesskey' => 'p',
 		];
 
 		if (Feature::get()) {
 			$tabs[] = [
-				'label' => DI::l10n()->t('Additional features'),
-				'url' => 'settings/features',
-				'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'features') ? 'active' : ''),
+				'label'     => $this->t('Additional features'),
+				'url'       => 'settings/features',
+				'selected'  => static::class == Settings\Features::class ? 'active' : '',
 				'accesskey' => 't',
 			];
 		}
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Display'),
-			'url' => 'settings/display',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'display') ? 'active' : ''),
+			'label'     => $this->t('Display'),
+			'url'       => 'settings/display',
+			'selected'  => static::class == Settings\Display::class ? 'active' : '',
 			'accesskey' => 'i',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Social Networks'),
-			'url' => 'settings/connectors',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'connectors') ? 'active' : ''),
+			'label'     => $this->t('Social Networks'),
+			'url'       => 'settings/connectors',
+			'selected'  => static::class == Settings\SocialNetworks::class ? 'active' : '',
 			'accesskey' => 'w',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Addons'),
-			'url' => 'settings/addon',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'addon') ? 'active' : ''),
+			'label'     => $this->t('Addons'),
+			'url'       => 'settings/addon',
+			'selected'  => static::class == Settings\Addons::class ? 'active' : '',
 			'accesskey' => 'l',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Manage Accounts'),
-			'url' => 'settings/delegation',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'delegation') ? 'active' : ''),
+			'label'     => $this->t('Manage Accounts'),
+			'url'       => 'settings/delegation',
+			'selected'  => static::class == Settings\Delegation::class ? 'active' : '',
 			'accesskey' => 'd',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Connected apps'),
-			'url' => 'settings/oauth',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'oauth') ? 'active' : ''),
+			'label'     => $this->t('Connected apps'),
+			'url'       => 'settings/oauth',
+			'selected'  => static::class == Settings\OAuth::class ? 'active' : '',
 			'accesskey' => 'b',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Export personal data'),
-			'url' => 'settings/userexport',
-			'selected' => ((DI::args()->getArgc() > 1) && (DI::args()->getArgv()[1] === 'userexport') ? 'active' : ''),
+			'label'     => $this->t('Export personal data'),
+			'url'       => 'settings/userexport',
+			'selected'  => static::class == Settings\UserExport::class ? 'active' : '',
 			'accesskey' => 'e',
 		];
 
 		$tabs[] = [
-			'label' => DI::l10n()->t('Remove account'),
-			'url' => 'settings/removeme',
-			'selected' => static::class === Settings\RemoveMe::class ? 'active' : '',
+			'label'     => $this->t('Remove account'),
+			'url'       => 'settings/removeme',
+			'selected'  => static::class === Settings\RemoveMe::class ? 'active' : '',
 			'accesskey' => 'r',
 		];
 
 
-		$tabtpl = Renderer::getMarkupTemplate("generic_links_widget.tpl");
-		DI::page()['aside'] = Renderer::replaceMacros($tabtpl, [
-			'$title' => DI::l10n()->t('Settings'),
+		$tabtpl              = Renderer::getMarkupTemplate('generic_links_widget.tpl');
+		$this->page['aside'] = Renderer::replaceMacros($tabtpl, [
+			'$title' => $this->t('Settings'),
 			'$class' => 'settings-widget',
 			'$items' => $tabs,
 		]);
 	}
-
-	protected function content(array $request = []): string
-	{
-		$a = DI::app();
-
-		static::createAside();
-
-		return '';
-	}
 }
diff --git a/src/Module/Settings/OAuth.php b/src/Module/Settings/OAuth.php
new file mode 100644
index 0000000000..d14fb05885
--- /dev/null
+++ b/src/Module/Settings/OAuth.php
@@ -0,0 +1,80 @@
+<?php
+/**
+ * @copyright Copyright (C) 2010-2022, the Friendica project
+ *
+ * @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 <https://www.gnu.org/licenses/>.
+ *
+ */
+
+namespace Friendica\Module\Settings;
+
+use Friendica\App;
+use Friendica\Core\L10n;
+use Friendica\Core\Renderer;
+use Friendica\Core\Session\Capability\IHandleUserSessions;
+use Friendica\Database\Database;
+use Friendica\Module\BaseSettings;
+use Friendica\Module\Response;
+use Friendica\Util\Profiler;
+use Psr\Log\LoggerInterface;
+
+class OAuth extends BaseSettings
+{
+	/** @var Database */
+	private $database;
+
+	public function __construct(Database $database, IHandleUserSessions $session, App\Page $page, L10n $l10n, App\BaseURL $baseUrl, App\Arguments $args, LoggerInterface $logger, Profiler $profiler, Response $response, array $server, array $parameters = [])
+	{
+		parent::__construct($session, $page, $l10n, $baseUrl, $args, $logger, $profiler, $response, $server, $parameters);
+
+		$this->database = $database;
+	}
+
+	protected function post(array $request = [])
+	{
+		if (!$this->session->getLocalUserId()) {
+			return;
+		}
+
+		if (!isset($request['delete'])) {
+			return;
+		}
+
+		BaseSettings::checkFormSecurityTokenRedirectOnError('/settings/oauth', 'settings_oauth');
+
+		$this->database->delete('application-token', ['application-id' => $request['delete'], 'uid' => $this->session->getLocalUserId()]);
+		$this->baseUrl->redirect('settings/oauth', true);
+	}
+
+	protected function content(array $request = []): string
+	{
+		parent::content($request);
+
+		$applications = $this->database->selectToArray('application-view', ['id', 'uid', 'name', 'website', 'scopes', 'created_at'], ['uid' => $this->session->getLocalUserId()]);
+
+		$tpl = Renderer::getMarkupTemplate('settings/oauth.tpl');
+		return Renderer::replaceMacros($tpl, [
+			'$form_security_token' => BaseSettings::getFormSecurityToken('settings_oauth'),
+			'$baseurl'             => $this->baseUrl->get(true),
+			'$title'               => $this->t('Connected Apps'),
+			'$name'                => $this->t('Name'),
+			'$website'             => $this->t('Home Page'),
+			'$created_at'          => $this->t('Created'),
+			'$delete'              => $this->t('Remove authorization'),
+			'$apps'                => $applications,
+		]);
+	}
+}
diff --git a/static/routes.config.php b/static/routes.config.php
index d281f252b9..c69a1515af 100644
--- a/static/routes.config.php
+++ b/static/routes.config.php
@@ -600,7 +600,8 @@ return [
 			'/trusted'      => [Module\Settings\TwoFactor\Trusted::class, [R::GET, R::POST]],
 		],
 		'/delegation[/{action}/{user_id}]' => [Module\Settings\Delegation::class,       [R::GET, R::POST]],
-		'/display'                 => [Module\Settings\Display::class,             [R::GET, R::POST]],
+		'/display'                         => [Module\Settings\Display::class,          [R::GET, R::POST]],
+		'/oauth'                           => [Module\Settings\OAuth::class,            [R::GET, R::POST]],
 		'/profile' => [
 			'[/]'                  => [Module\Settings\Profile\Index::class,       [R::GET, R::POST]],
 			'/photo[/new]'         => [Module\Settings\Profile\Photo\Index::class, [R::GET, R::POST]],
diff --git a/view/templates/settings/oauth.tpl b/view/templates/settings/oauth.tpl
index 955b5754dc..ad8407fb17 100644
--- a/view/templates/settings/oauth.tpl
+++ b/view/templates/settings/oauth.tpl
@@ -1,13 +1,14 @@
 <div class="generic-page-wrapper">
 	<h1>{{$title}}</h1>
 	<form action="settings/oauth" method="post" autocomplete="off">
-		<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
+		<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
 		<table id='application-block' class='table table-condensed table-striped'>
 			<thead>
 				<tr>
 					<th>{{$name}}</th>
 					<th>{{$website}}</th>
 					<th>{{$created_at}}</th>
+					<th></th>
 				</tr>
 			</thead>
 			<tbody>
@@ -16,7 +17,11 @@
 					<td>{{$app.name}}</td>
 					<td>{{$app.website}}</td>
 					<td>{{$app.created_at}}</td>
-					<td><a href="{{$baseurl}}/settings/oauth/delete/{{$app.id}}?t={{$form_security_token}}" class="icon s22 delete" title="{{$delete}}">&nbsp;</a></td>
+					<td>
+						<button type="submit" class="btn" title="{{$delete}}" name="delete" value="{{$app.id}}">
+							<i class="icon s22 delete" aria-hidden="true"></i>
+						</button>
+					</td>
 				</tr>
 				{{/foreach}}
 			</tbody>
diff --git a/view/theme/frio/templates/settings/oauth.tpl b/view/theme/frio/templates/settings/oauth.tpl
index 98cb96a3f8..547203a7b0 100644
--- a/view/theme/frio/templates/settings/oauth.tpl
+++ b/view/theme/frio/templates/settings/oauth.tpl
@@ -2,13 +2,14 @@
 	{{* include the title template for the settings title *}}
 	{{include file="section_title.tpl" title=$title}}
 	<form action="settings/oauth" method="post" autocomplete="off">
-		<input type='hidden' name='form_security_token' value='{{$form_security_token}}'>
+		<input type="hidden" name="form_security_token" value="{{$form_security_token}}">
 		<table id='application-block' class='table table-condensed table-striped'>
 			<thead>
 				<tr>
 					<th>{{$name}}</th>
 					<th>{{$website}}</th>
 					<th>{{$created_at}}</th>
+					<th></th>
 				</tr>
 			</thead>
 			<tbody>
@@ -17,7 +18,11 @@
 					<td>{{$app.name}}</td>
 					<td>{{$app.website}}</td>
 					<td>{{$app.created_at}}</td>
-					<td><a href="{{$baseurl}}/settings/oauth/delete/{{$app.id}}?t={{$form_security_token}}" class="btn" title="{{$delete}}"><i class="fa fa-trash" aria-hidden="true"></i></a></td>
+					<td>
+						<button type="submit" class="btn" title="{{$delete}}" name="delete" value="{{$app.id}}">
+							<i class="fa fa-trash" aria-hidden="true"></i>
+						</button>
+					</td>
 				</tr>
 				{{/foreach}}
 			</tbody>