diff --git a/apps/files_sharing/appinfo/routes.php b/apps/files_sharing/appinfo/routes.php
index 863b27da2777ecb337554b38ba4f30a18e4e0e8f..8e5110c6a16507b852af21dc726afec8232a75a5 100644
--- a/apps/files_sharing/appinfo/routes.php
+++ b/apps/files_sharing/appinfo/routes.php
@@ -34,13 +34,7 @@ return [
 		],
 		[
 			'name' => 'PublicPreview#getPreview',
-			'url' => '/publicpreview',
-			'verb' => 'GET',
-		],
-
-		[
-			'name' => 'PublicPreview#getPreview',
-			'url' => '/ajax/publicpreview.php',
+			'url' => '/publicpreview/{token}',
 			'verb' => 'GET',
 		],
 
diff --git a/apps/files_sharing/js/public.js b/apps/files_sharing/js/public.js
index 1de7c6b4fcdcf7507dea00df999b17e1a0d62088..a2884468a6b9d93d271a34f59a23999184d94b71 100644
--- a/apps/files_sharing/js/public.js
+++ b/apps/files_sharing/js/public.js
@@ -112,7 +112,6 @@ OCA.Sharing.PublicApp = {
 			y: Math.ceil(previewHeight * window.devicePixelRatio),
 			a: 'true',
 			file: encodeURIComponent(this.initialDir + $('#filename').val()),
-			t: token,
 			scalingup: 0
 		};
 
@@ -150,7 +149,7 @@ OCA.Sharing.PublicApp = {
 		} else if ((previewSupported === 'true' && mimetype.substr(0, mimetype.indexOf('/')) !== 'video') ||
 			mimetype.substr(0, mimetype.indexOf('/')) === 'image' &&
 			mimetype !== 'image/svg+xml') {
-			img.attr('src', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
+			img.attr('src', OC.linkTo('files_sharing', '/publicpreview/'+token) + '?' + OC.buildQueryString(params));
 			imgcontainer.appendTo('#imgframe');
 		} else if (mimetype.substr(0, mimetype.indexOf('/')) !== 'video') {
 			img.attr('src', OC.Util.replaceSVGIcon(mimetypeIcon));
@@ -158,7 +157,7 @@ OCA.Sharing.PublicApp = {
 			imgcontainer.appendTo('#imgframe');
 		}
 		else if (previewSupported === 'true') {
-			$('#imgframe > video').attr('poster', OC.filePath('files_sharing', 'ajax', 'publicpreview.php') + '?' + OC.buildQueryString(params));
+			$('#imgframe > video').attr('poster', OC.generateUrl(OC.linkTo('files_sharing', '/publicpreview/'+token)) + '?' + OC.buildQueryString(params));
 		}
 
 		if (this.fileList) {
@@ -223,8 +222,8 @@ OCA.Sharing.PublicApp = {
 				urlSpec.y *= window.devicePixelRatio;
 				urlSpec.x = Math.ceil(urlSpec.x);
 				urlSpec.y = Math.ceil(urlSpec.y);
-				urlSpec.t = $('#dirToken').val();
-				return OC.generateUrl('/apps/files_sharing/ajax/publicpreview.php?') + $.param(urlSpec);
+				var token = $('#dirToken').val();
+				return OC.generateUrl(OC.linkTo('files_sharing', '/publicpreview/'+token) + '?' + OC.buildQueryString(urlSpec));
 			};
 
 			this.fileList.updateEmptyContent = function() {
@@ -427,4 +426,4 @@ $(document).ready(function () {
 		};
 	}
 
-});
\ No newline at end of file
+});
diff --git a/apps/files_sharing/lib/Controller/PublicPreviewController.php b/apps/files_sharing/lib/Controller/PublicPreviewController.php
index 0870995fc7b1606adacb35b13de6da1434040df3..b13c0a64b0e8a563dc83da2ab131ad1d395665c5 100644
--- a/apps/files_sharing/lib/Controller/PublicPreviewController.php
+++ b/apps/files_sharing/lib/Controller/PublicPreviewController.php
@@ -27,15 +27,18 @@ use OCP\AppFramework\Controller;
 use OCP\AppFramework\Http;
 use OCP\AppFramework\Http\DataResponse;
 use OCP\AppFramework\Http\FileDisplayResponse;
+use OCP\AppFramework\PublicShareController;
 use OCP\Constants;
 use OCP\Files\Folder;
 use OCP\Files\NotFoundException;
 use OCP\IPreview;
 use OCP\IRequest;
+use OCP\ISession;
 use OCP\Share\Exceptions\ShareNotFound;
 use OCP\Share\IManager as ShareManager;
+use OCP\Share\IShare;
 
-class PublicPreviewController extends Controller {
+class PublicPreviewController extends PublicShareController {
 
 	/** @var ShareManager */
 	private $shareManager;
@@ -43,16 +46,38 @@ class PublicPreviewController extends Controller {
 	/** @var IPreview */
 	private $previewManager;
 
-	public function __construct($appName,
+	/** @var IShare */
+	private $share;
+
+	public function __construct(string $appName,
 								IRequest $request,
 								ShareManager $shareManger,
+								ISession $session,
 								IPreview $previewManager) {
-		parent::__construct($appName, $request);
+		parent::__construct($appName, $request, $session);
 
 		$this->shareManager = $shareManger;
 		$this->previewManager = $previewManager;
 	}
 
+	protected function getPasswordHash(): string {
+		return $this->share->getPassword();
+	}
+
+	public function isValidToken(): bool {
+		try {
+			$this->share = $this->shareManager->getShareByToken($this->getToken());
+			return true;
+		} catch (ShareNotFound $e) {
+			return false;
+		}
+	}
+
+	protected function isPasswordProtected(): bool {
+		return $this->share->getPassword() !== null;
+	}
+
+
 	/**
 	 * @PublicPage
 	 * @NoCSRFRequired
@@ -60,24 +85,23 @@ class PublicPreviewController extends Controller {
 	 * @param string $file
 	 * @param int $x
 	 * @param int $y
-	 * @param string $t
 	 * @param bool $a
 	 * @return DataResponse|FileDisplayResponse
 	 */
 	public function getPreview(
-		$file = '',
-		$x = 32,
-		$y = 32,
-		$t = '',
+		string $token,
+		string $file = '',
+		int $x = 32,
+		int $y = 32,
 		$a = false
 	) {
 
-		if ($t === '' || $x === 0 || $y === 0) {
+		if ($token === '' || $x === 0 || $y === 0) {
 			return new DataResponse([], Http::STATUS_BAD_REQUEST);
 		}
 
 		try {
-			$share = $this->shareManager->getShareByToken($t);
+			$share = $this->shareManager->getShareByToken($token);
 		} catch (ShareNotFound $e) {
 			return new DataResponse([], Http::STATUS_NOT_FOUND);
 		}
diff --git a/apps/files_sharing/lib/Controller/ShareController.php b/apps/files_sharing/lib/Controller/ShareController.php
index 739031d4bc27f5181e0a96c0f6ac684dddfb7f57..0b30a599c7ffa3cca90d7dbd7e6e5db15819aacc 100644
--- a/apps/files_sharing/lib/Controller/ShareController.php
+++ b/apps/files_sharing/lib/Controller/ShareController.php
@@ -38,6 +38,7 @@ namespace OCA\Files_Sharing\Controller;
 use OC_Files;
 use OC_Util;
 use OCA\FederatedFileSharing\FederatedShareProvider;
+use OCP\AppFramework\AuthPublicShareController;
 use OCP\AppFramework\Http\Template\SimpleMenuAction;
 use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
 use OCP\AppFramework\Http\Template\LinkMenuAction;
@@ -46,10 +47,8 @@ use OCP\Defaults;
 use OCP\IL10N;
 use OCP\Template;
 use OCP\Share;
-use OCP\AppFramework\Controller;
 use OCP\IRequest;
 use OCP\AppFramework\Http\TemplateResponse;
-use OCP\AppFramework\Http\RedirectResponse;
 use OCP\AppFramework\Http\NotFoundResponse;
 use OCP\IURLGenerator;
 use OCP\IConfig;
@@ -58,32 +57,27 @@ use OCP\IUserManager;
 use OCP\ISession;
 use OCP\IPreview;
 use OCA\Files_Sharing\Activity\Providers\Downloads;
-use \OCP\Files\NotFoundException;
+use OCP\Files\NotFoundException;
 use OCP\Files\IRootFolder;
 use OCP\Share\Exceptions\ShareNotFound;
 use Symfony\Component\EventDispatcher\EventDispatcherInterface;
+use OCP\Share\IManager as ShareManager;
 
 /**
  * Class ShareController
  *
  * @package OCA\Files_Sharing\Controllers
  */
-class ShareController extends Controller {
+class ShareController extends AuthPublicShareController {
 
 	/** @var IConfig */
 	protected $config;
-	/** @var IURLGenerator */
-	protected $urlGenerator;
 	/** @var IUserManager */
 	protected $userManager;
 	/** @var ILogger */
 	protected $logger;
 	/** @var \OCP\Activity\IManager */
 	protected $activityManager;
-	/** @var \OCP\Share\IManager */
-	protected $shareManager;
-	/** @var ISession */
-	protected $session;
 	/** @var IPreview */
 	protected $previewManager;
 	/** @var IRootFolder */
@@ -96,6 +90,11 @@ class ShareController extends Controller {
 	protected $l10n;
 	/** @var Defaults */
 	protected $defaults;
+	/** @var ShareManager */
+	protected $shareManager;
+
+	/** @var Share\IShare */
+	protected $share;
 
 	/**
 	 * @param string $appName
@@ -114,14 +113,14 @@ class ShareController extends Controller {
 	 * @param IL10N $l10n
 	 * @param Defaults $defaults
 	 */
-	public function __construct($appName,
+	public function __construct(string $appName,
 								IRequest $request,
 								IConfig $config,
 								IURLGenerator $urlGenerator,
 								IUserManager $userManager,
 								ILogger $logger,
 								\OCP\Activity\IManager $activityManager,
-								\OCP\Share\IManager $shareManager,
+								ShareManager $shareManager,
 								ISession $session,
 								IPreview $previewManager,
 								IRootFolder $rootFolder,
@@ -129,108 +128,50 @@ class ShareController extends Controller {
 								EventDispatcherInterface $eventDispatcher,
 								IL10N $l10n,
 								Defaults $defaults) {
-		parent::__construct($appName, $request);
+		parent::__construct($appName, $request, $session, $urlGenerator);
 
 		$this->config = $config;
-		$this->urlGenerator = $urlGenerator;
 		$this->userManager = $userManager;
 		$this->logger = $logger;
 		$this->activityManager = $activityManager;
-		$this->shareManager = $shareManager;
-		$this->session = $session;
 		$this->previewManager = $previewManager;
 		$this->rootFolder = $rootFolder;
 		$this->federatedShareProvider = $federatedShareProvider;
 		$this->eventDispatcher = $eventDispatcher;
 		$this->l10n = $l10n;
 		$this->defaults = $defaults;
+		$this->shareManager = $shareManager;
 	}
 
-	/**
-	 * @PublicPage
-	 * @NoCSRFRequired
-	 *
-	 * @param string $token
-	 * @return TemplateResponse|RedirectResponse
-	 */
-	public function showAuthenticate($token) {
-		$share = $this->shareManager->getShareByToken($token);
-
-		if($this->linkShareAuth($share)) {
-			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.showShare', array('token' => $token)));
-		}
-
-		return new TemplateResponse($this->appName, 'authenticate', array(), 'guest');
+	protected function verifyPassword(string $password): bool {
+		return $this->shareManager->checkPassword($this->share, $password);
 	}
 
-	/**
-	 * @PublicPage
-	 * @UseSession
-	 * @BruteForceProtection(action=publicLinkAuth)
-	 *
-	 * Authenticates against password-protected shares
-	 * @param string $token
-	 * @param string $redirect
-	 * @param string $password
-	 * @return RedirectResponse|TemplateResponse|NotFoundResponse
-	 */
-	public function authenticate($token, $redirect, $password = '') {
+	protected function getPasswordHash(): string {
+		return $this->share->getPassword();
+	}
 
-		// Check whether share exists
+	public function isValidToken(): bool {
 		try {
-			$share = $this->shareManager->getShareByToken($token);
+			$this->share = $this->shareManager->getShareByToken($this->getToken());
 		} catch (ShareNotFound $e) {
-			return new NotFoundResponse();
+			return false;
 		}
 
-		$authenticate = $this->linkShareAuth($share, $password);
-
-		// if download was requested before auth, redirect to download
-		if ($authenticate === true && $redirect === 'download') {
-			return new RedirectResponse($this->urlGenerator->linkToRoute(
-				'files_sharing.sharecontroller.downloadShare',
-				array('token' => $token))
-			);
-		} else if ($authenticate === true) {
-			return new RedirectResponse($this->urlGenerator->linkToRoute(
-				'files_sharing.sharecontroller.showShare',
-				array('token' => $token))
-			);
-		}
+		return true;
+	}
 
-		$response = new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest');
-		$response->throttle();
-		return $response;
+	protected function isPasswordProtected(): bool {
+		return $this->share->getPassword() !== null;
 	}
 
-	/**
-	 * Authenticate a link item with the given password.
-	 * Or use the session if no password is provided.
-	 *
-	 * This is a modified version of Helper::authenticate
-	 * TODO: Try to merge back eventually with Helper::authenticate
-	 *
-	 * @param \OCP\Share\IShare $share
-	 * @param string|null $password
-	 * @return bool
-	 */
-	private function linkShareAuth(\OCP\Share\IShare $share, $password = null) {
-		if ($password !== null) {
-			if ($this->shareManager->checkPassword($share, $password)) {
-				$this->session->regenerateId(true, true);
-				$this->session->set('public_link_authenticated', (string)$share->getId());
-			} else {
-				$this->emitAccessShareHook($share, 403, 'Wrong password');
-				return false;
-			}
-		} else {
-			// not authenticated ?
-			if ( ! $this->session->exists('public_link_authenticated')
-				|| $this->session->get('public_link_authenticated') !== (string)$share->getId()) {
-				return false;
-			}
-		}
-		return true;
+	protected function authSucceeded() {
+		// For share this was always set so it is still used in other apps
+		$this->session->set('public_link_authenticated', (string)$this->share->getId());
+	}
+
+	protected function authFailed() {
+		$this->emitAccessShareHook($this->share, 403, 'Wrong password');
 	}
 
 	/**
@@ -285,27 +226,21 @@ class ShareController extends Controller {
 	 * @PublicPage
 	 * @NoCSRFRequired
 	 *
-	 * @param string $token
+
 	 * @param string $path
-	 * @return TemplateResponse|RedirectResponse|NotFoundResponse
+	 * @return TemplateResponse
 	 * @throws NotFoundException
 	 * @throws \Exception
 	 */
-	public function showShare($token, $path = '') {
+	public function showShare($path = ''): TemplateResponse {
 		\OC_User::setIncognitoMode(true);
 
 		// Check whether share exists
 		try {
-			$share = $this->shareManager->getShareByToken($token);
+			$share = $this->shareManager->getShareByToken($this->getToken());
 		} catch (ShareNotFound $e) {
-			$this->emitAccessShareHook($token, 404, 'Share not found');
-			return new NotFoundResponse();
-		}
-
-		// Share is password protected - check whether the user is permitted to access the share
-		if ($share->getPassword() !== null && !$this->linkShareAuth($share)) {
-			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate',
-				array('token' => $token, 'redirect' => 'preview')));
+			$this->emitAccessShareHook($this->getToken(), 404, 'Share not found');
+			throw new NotFoundException();
 		}
 
 		if (!$this->validateShare($share)) {
@@ -329,8 +264,8 @@ class ShareController extends Controller {
 		$shareTmpl['directory_path'] = $share->getTarget();
 		$shareTmpl['mimetype'] = $share->getNode()->getMimetype();
 		$shareTmpl['previewSupported'] = $this->previewManager->isMimeSupported($share->getNode()->getMimetype());
-		$shareTmpl['dirToken'] = $token;
-		$shareTmpl['sharingToken'] = $token;
+		$shareTmpl['dirToken'] = $this->getToken();
+		$shareTmpl['sharingToken'] = $this->getToken();
 		$shareTmpl['server2serversharing'] = $this->federatedShareProvider->isOutgoingServer2serverShareEnabled();
 		$shareTmpl['protected'] = $share->getPassword() !== null ? 'true' : 'false';
 		$shareTmpl['dir'] = '';
@@ -367,7 +302,7 @@ class ShareController extends Controller {
 
 			$folder = new Template('files', 'list', '');
 			$folder->assign('dir', $rootFolder->getRelativePath($folderNode->getPath()));
-			$folder->assign('dirToken', $token);
+			$folder->assign('dirToken', $this->getToken());
 			$folder->assign('permissions', \OCP\Constants::PERMISSION_READ);
 			$folder->assign('isPublic', true);
 			$folder->assign('hideFileList', $hideFileList);
@@ -382,8 +317,8 @@ class ShareController extends Controller {
 
 		$shareTmpl['hideFileList'] = $hideFileList;
 		$shareTmpl['shareOwner'] = $this->userManager->get($share->getShareOwner())->getDisplayName();
-		$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $token]);
-		$shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $token]);
+		$shareTmpl['downloadURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.downloadShare', ['token' => $this->getToken()]);
+		$shareTmpl['shareUrl'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.sharecontroller.showShare', ['token' => $this->getToken()]);
 		$shareTmpl['maxSizeAnimateGif'] = $this->config->getSystemValue('max_filesize_animated_gifs_public_sharing', 10);
 		$shareTmpl['previewEnabled'] = $this->config->getSystemValue('enable_previews', true);
 		$shareTmpl['previewMaxX'] = $this->config->getSystemValue('preview_max_x', 1024);
@@ -393,19 +328,19 @@ class ShareController extends Controller {
 		$ogPreview = '';
 		if ($shareTmpl['previewSupported']) {
 			$shareTmpl['previewImage'] = $this->urlGenerator->linkToRouteAbsolute( 'files_sharing.PublicPreview.getPreview',
-				['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 't' => $shareTmpl['dirToken']]);
+				['x' => 200, 'y' => 200, 'file' => $shareTmpl['directory_path'], 'token' => $shareTmpl['dirToken']]);
 			$ogPreview = $shareTmpl['previewImage'];
 
 			// We just have direct previews for image files
 			if ($share->getNode()->getMimePart() === 'image') {
-				$shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $token]);
+				$shareTmpl['previewURL'] = $this->urlGenerator->linkToRouteAbsolute('files_sharing.publicpreview.directLink', ['token' => $this->getToken()]);
 
 				$ogPreview = $shareTmpl['previewURL'];
 
 				//Whatapp is kind of picky about their size requirements
 				if ($this->request->isUserAgent(['/^WhatsApp/'])) {
 					$ogPreview = $this->urlGenerator->linkToRouteAbsolute('files_sharing.PublicPreview.getPreview', [
-						't' => $token,
+						'token' => $this->getToken(),
 						'x' => 256,
 						'y' => 256,
 						'a' => true,
@@ -488,12 +423,6 @@ class ShareController extends Controller {
 			return new \OCP\AppFramework\Http\DataResponse('Share is read-only');
 		}
 
-		// Share is password protected - check whether the user is permitted to access the share
-		if ($share->getPassword() !== null && !$this->linkShareAuth($share)) {
-			return new RedirectResponse($this->urlGenerator->linkToRoute('files_sharing.sharecontroller.authenticate',
-				['token' => $token, 'redirect' => 'download']));
-		}
-
 		$files_list = null;
 		if (!is_null($files)) { // download selected files
 			$files_list = json_decode($files);
@@ -507,13 +436,15 @@ class ShareController extends Controller {
 			}
 		}
 
-		$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
-		$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
 
 		if (!$this->validateShare($share)) {
 			throw new NotFoundException();
 		}
 
+		$userFolder = $this->rootFolder->getUserFolder($share->getShareOwner());
+		$originalSharePath = $userFolder->getRelativePath($share->getNode()->getPath());
+
+
 		// Single file share
 		if ($share->getNode() instanceof \OCP\Files\File) {
 			// Single file download
diff --git a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php
index 4b630d0a8dab67de8c8d05333d2c3ae9335db24f..b5f1178b7f08aa923de426d05adc280f3bb9cb37 100644
--- a/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php
+++ b/apps/files_sharing/lib/Middleware/SharingCheckMiddleware.php
@@ -101,13 +101,6 @@ class SharingCheckMiddleware extends Middleware {
 		if ($controller instanceof ExternalSharesController &&
 			!$this->externalSharesChecks()) {
 			throw new S2SException('Federated sharing not allowed');
-		} else if ($controller instanceof ShareController) {
-			$token = $this->request->getParam('token');
-			$share = $this->shareManager->getShareByToken($token);
-			if ($share->getShareType() === \OCP\Share::SHARE_TYPE_LINK
-				&& !$this->isLinkSharingEnabled()) {
-				throw new NotFoundException('Link sharing is disabled');
-			}
 		}
 	}
 
@@ -165,22 +158,6 @@ class SharingCheckMiddleware extends Middleware {
 		return true;
 	}
 
-	/**
-	 * Check if link sharing is allowed
-	 * @return bool
-	 */
-	private function isLinkSharingEnabled() {
-		// Check if the shareAPI is enabled
-		if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') {
-			return false;
-		}
 
-		// Check whether public sharing is enabled
-		if($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
-			return false;
-		}
-
-		return true;
-	}
 
 }
diff --git a/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php b/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php
index 174abbb6f609556d5e25166638888de1dbe0357d..27e13bc8ced1050638678ae2e38af43e0057234e 100644
--- a/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php
+++ b/apps/files_sharing/tests/Controller/PublicPreviewControllerTest.php
@@ -33,6 +33,7 @@ use OCP\Files\NotFoundException;
 use OCP\Files\SimpleFS\ISimpleFile;
 use OCP\IPreview;
 use OCP\IRequest;
+use OCP\ISession;
 use OCP\Share\Exceptions\ShareNotFound;
 use OCP\Share\IManager;
 use OCP\Share\IShare;
@@ -60,26 +61,27 @@ class PublicPreviewControllerTest extends TestCase {
 			'files_sharing',
 			$this->createMock(IRequest::class),
 			$this->shareManager,
+			$this->createMock(ISession::class),
 			$this->previewManager
 		);
 	}
 
 	public function testInvalidToken() {
-		$res = $this->controller->getPreview('file', 10, 10, '');
+		$res = $this->controller->getPreview('', 'file', 10, 10, '');
 		$expected = new DataResponse([], Http::STATUS_BAD_REQUEST);
 
 		$this->assertEquals($expected, $res);
 	}
 
 	public function testInvalidWidth() {
-		$res = $this->controller->getPreview('file', 0);
+		$res = $this->controller->getPreview('token', 'file', 0);
 		$expected = new DataResponse([], Http::STATUS_BAD_REQUEST);
 
 		$this->assertEquals($expected, $res);
 	}
 
 	public function testInvalidHeight() {
-		$res = $this->controller->getPreview('file', 10, 0);
+		$res = $this->controller->getPreview('token', 'file', 10, 0);
 		$expected = new DataResponse([], Http::STATUS_BAD_REQUEST);
 
 		$this->assertEquals($expected, $res);
@@ -90,7 +92,7 @@ class PublicPreviewControllerTest extends TestCase {
 			->with($this->equalTo('token'))
 			->willThrowException(new ShareNotFound());
 
-		$res = $this->controller->getPreview('file', 10, 10, 'token');
+		$res = $this->controller->getPreview('token', 'file', 10, 10);
 		$expected = new DataResponse([], Http::STATUS_NOT_FOUND);
 
 		$this->assertEquals($expected, $res);
@@ -105,7 +107,7 @@ class PublicPreviewControllerTest extends TestCase {
 		$share->method('getPermissions')
 			->willReturn(0);
 
-		$res = $this->controller->getPreview('file', 10, 10, 'token');
+		$res = $this->controller->getPreview('token', 'file', 10, 10);
 		$expected = new DataResponse([], Http::STATUS_FORBIDDEN);
 
 		$this->assertEquals($expected, $res);
@@ -132,7 +134,7 @@ class PublicPreviewControllerTest extends TestCase {
 		$preview->method('getMimeType')
 			->willReturn('myMime');
 
-		$res = $this->controller->getPreview('file', 10, 10, 'token', true);
+		$res = $this->controller->getPreview('token', 'file', 10, 10, true);
 		$expected = new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => 'myMime']);
 		$this->assertEquals($expected, $res);
 	}
@@ -154,7 +156,7 @@ class PublicPreviewControllerTest extends TestCase {
 			->with($this->equalTo('file'))
 			->willThrowException(new NotFoundException());
 
-		$res = $this->controller->getPreview('file', 10, 10, 'token', true);
+		$res = $this->controller->getPreview('token', 'file', 10, 10, true);
 		$expected = new DataResponse([], Http::STATUS_NOT_FOUND);
 		$this->assertEquals($expected, $res);
 	}
@@ -186,7 +188,7 @@ class PublicPreviewControllerTest extends TestCase {
 		$preview->method('getMimeType')
 			->willReturn('myMime');
 
-		$res = $this->controller->getPreview('file', 10, 10, 'token', true);
+		$res = $this->controller->getPreview('token', 'file', 10, 10, true);
 		$expected = new FileDisplayResponse($preview, Http::STATUS_OK, ['Content-Type' => 'myMime']);
 		$this->assertEquals($expected, $res);
 	}
diff --git a/apps/files_sharing/tests/Controller/ShareControllerTest.php b/apps/files_sharing/tests/Controller/ShareControllerTest.php
index be99c5ee1944d3316175f3a379ac0389039f7196..fb417878647b03970ef76b7857aaed05849ceceb 100644
--- a/apps/files_sharing/tests/Controller/ShareControllerTest.php
+++ b/apps/files_sharing/tests/Controller/ShareControllerTest.php
@@ -39,6 +39,7 @@ use OCP\AppFramework\Http\Template\ExternalShareMenuAction;
 use OCP\AppFramework\Http\Template\LinkMenuAction;
 use OCP\AppFramework\Http\Template\PublicTemplateResponse;
 use OCP\AppFramework\Http\Template\SimpleMenuAction;
+use OCP\Files\NotFoundException;
 use OCP\IConfig;
 use OCP\IL10N;
 use OCP\ILogger;
@@ -156,193 +157,24 @@ class ShareControllerTest extends \Test\TestCase {
 		parent::tearDown();
 	}
 
-	public function testShowAuthenticateNotAuthenticated() {
-		$share = \OC::$server->getShareManager()->newShare();
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$response = $this->shareController->showAuthenticate('token');
-		$expectedResponse =  new TemplateResponse($this->appName, 'authenticate', [], 'guest');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testShowAuthenticateAuthenticatedForDifferentShare() {
-		$share = \OC::$server->getShareManager()->newShare();
-		$share->setId(1);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('2');
-
-		$response = $this->shareController->showAuthenticate('token');
-		$expectedResponse =  new TemplateResponse($this->appName, 'authenticate', [], 'guest');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testShowAuthenticateCorrectShare() {
-		$share = \OC::$server->getShareManager()->newShare();
-		$share->setId(1);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$this->session->method('exists')->with('public_link_authenticated')->willReturn(true);
-		$this->session->method('get')->with('public_link_authenticated')->willReturn('1');
-
-		$this->urlGenerator->expects($this->once())
-			->method('linkToRoute')
-			->with('files_sharing.sharecontroller.showShare', ['token' => 'token'])
-			->willReturn('redirect');
-
-		$response = $this->shareController->showAuthenticate('token');
-		$expectedResponse =  new RedirectResponse('redirect');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testAuthenticateInvalidToken() {
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->will($this->throwException(new \OCP\Share\Exceptions\ShareNotFound()));
-
-		$response = $this->shareController->authenticate('token', 'preview');
-		$expectedResponse =  new NotFoundResponse();
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testAuthenticateValidPassword() {
-		$share = \OC::$server->getShareManager()->newShare();
-		$share->setId(42);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('checkPassword')
-			->with($share, 'validpassword')
-			->willReturn(true);
-
-		$this->session
-			->expects($this->once())
-			->method('set')
-			->with('public_link_authenticated', '42');
-
-		$this->urlGenerator->expects($this->once())
-			->method('linkToRoute')
-			->with('files_sharing.sharecontroller.showShare', ['token'=>'token'])
-			->willReturn('redirect');
-
-		$response = $this->shareController->authenticate('token', 'preview', 'validpassword');
-		$expectedResponse =  new RedirectResponse('redirect');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testAuthenticateValidPasswordAndDownload() {
-		$share = \OC::$server->getShareManager()->newShare();
-		$share->setId(42);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('checkPassword')
-			->with($share, 'validpassword')
-			->willReturn(true);
-
-		$this->session
-			->expects($this->once())
-			->method('set')
-			->with('public_link_authenticated', '42');
-
-		$this->urlGenerator->expects($this->once())
-			->method('linkToRoute')
-			->with('files_sharing.sharecontroller.downloadShare', ['token'=>'token'])
-			->willReturn('redirect');
-
-		$response = $this->shareController->authenticate('token', 'download', 'validpassword');
-		$expectedResponse =  new RedirectResponse('redirect');
-		$this->assertEquals($expectedResponse, $response);
-	}
-
-	public function testAuthenticateInvalidPassword() {
-		$share = \OC::$server->getShareManager()->newShare();
-		$share->setNodeId(100)
-			->setNodeType('file')
-			->setToken('token')
-			->setSharedBy('initiator')
-			->setId(42);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('token')
-			->willReturn($share);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('checkPassword')
-			->with($share, 'invalidpassword')
-			->willReturn(false);
-
-		$this->session
-			->expects($this->never())
-			->method('set');
-
-		$hookListner = $this->getMockBuilder('Dummy')->setMethods(['access'])->getMock();
-		\OCP\Util::connectHook('OCP\Share', 'share_link_access',  $hookListner, 'access');
-
-		$hookListner->expects($this->once())
-			->method('access')
-			->with($this->callback(function(array $data) {
-				return $data['itemType'] === 'file' &&
-					$data['itemSource'] === 100 &&
-					$data['uidOwner'] === 'initiator' &&
-					$data['token'] === 'token' &&
-					$data['errorCode'] === 403 &&
-					$data['errorMessage'] === 'Wrong password';
-			}));
-
-		$response = $this->shareController->authenticate('token', 'preview', 'invalidpassword');
-		$expectedResponse =  new TemplateResponse($this->appName, 'authenticate', array('wrongpw' => true), 'guest');
-		$expectedResponse->throttle();
-		$this->assertEquals($expectedResponse, $response);
-	}
-
 	public function testShowShareInvalidToken() {
+		$this->shareController->setToken('invalidtoken');
+
 		$this->shareManager
 			->expects($this->once())
 			->method('getShareByToken')
 			->with('invalidtoken')
 			->will($this->throwException(new ShareNotFound()));
 
+		$this->expectException(NotFoundException::class);
+
 		// Test without a not existing token
-		$response = $this->shareController->showShare('invalidtoken');
-		$expectedResponse =  new NotFoundResponse();
-		$this->assertEquals($expectedResponse, $response);
+		$this->shareController->showShare();
 	}
 
 	public function testShowShareNotAuthenticated() {
+		$this->shareController->setToken('validtoken');
+
 		$share = \OC::$server->getShareManager()->newShare();
 		$share->setPassword('password');
 
@@ -352,19 +184,16 @@ class ShareControllerTest extends \Test\TestCase {
 			->with('validtoken')
 			->willReturn($share);
 
-		$this->urlGenerator->expects($this->once())
-			->method('linkToRoute')
-			->with('files_sharing.sharecontroller.authenticate', ['token' => 'validtoken', 'redirect' => 'preview'])
-			->willReturn('redirect');
+		$this->expectException(NotFoundException::class);
 
 		// Test without a not existing token
-		$response = $this->shareController->showShare('validtoken');
-		$expectedResponse = new RedirectResponse('redirect');
-		$this->assertEquals($expectedResponse, $response);
+		$this->shareController->showShare();
 	}
 
 
 	public function testShowShare() {
+		$this->shareController->setToken('token');
+
 		$owner = $this->getMockBuilder(IUser::class)->getMock();
 		$owner->method('getDisplayName')->willReturn('ownerDisplay');
 		$owner->method('getUID')->willReturn('ownerUID');
@@ -428,7 +257,7 @@ class ShareControllerTest extends \Test\TestCase {
 				return vsprintf($text, $parameters);
 			}));
 
-		$response = $this->shareController->showShare('token');
+		$response = $this->shareController->showShare();
 		$sharedTmplParams = array(
 			'displayName' => 'ownerDisplay',
 			'owner' => 'ownerUID',
@@ -476,6 +305,8 @@ class ShareControllerTest extends \Test\TestCase {
 	 * @expectedException \OCP\Files\NotFoundException
 	 */
 	public function testShowShareInvalid() {
+		$this->shareController->setToken('token');
+
 		$owner = $this->getMockBuilder(IUser::class)->getMock();
 		$owner->method('getDisplayName')->willReturn('ownerDisplay');
 		$owner->method('getUID')->willReturn('ownerUID');
@@ -517,32 +348,7 @@ class ShareControllerTest extends \Test\TestCase {
 
 		$this->userManager->method('get')->with('ownerUID')->willReturn($owner);
 
-		$this->shareController->showShare('token');
-	}
-
-	public function testDownloadShare() {
-		$share = $this->getMockBuilder(IShare::class)->getMock();
-		$share->method('getPassword')->willReturn('password');
-		$share
-			->expects($this->once())
-			->method('getPermissions')
-			->willReturn(\OCP\Constants::PERMISSION_READ);
-
-		$this->shareManager
-			->expects($this->once())
-			->method('getShareByToken')
-			->with('validtoken')
-			->willReturn($share);
-
-		$this->urlGenerator->expects($this->once())
-			->method('linkToRoute')
-			->with('files_sharing.sharecontroller.authenticate', ['token' => 'validtoken', 'redirect' => 'download'])
-			->willReturn('redirect');
-
-		// Test with a password protected share and no authentication
-		$response = $this->shareController->downloadShare('validtoken');
-		$expectedResponse = new RedirectResponse('redirect');
-		$this->assertEquals($expectedResponse, $response);
+		$this->shareController->showShare();
 	}
 
 	public function testDownloadShareWithCreateOnlyShare() {
diff --git a/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php b/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php
index d8676547a767349eb7105bca8c4c21401036ed10..1fea73e6b47f6a1ee473e33f6b930cf4c8abc6d4 100644
--- a/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php
+++ b/apps/files_sharing/tests/Middleware/SharingCheckMiddlewareTest.php
@@ -98,49 +98,6 @@ class SharingCheckMiddlewareTest extends \Test\TestCase {
 		$this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isSharingEnabled'));
 	}
 
-	public function testIsLinkSharingEnabledWithEverythinEnabled() {
-		$this->config
-			->expects($this->at(0))
-			->method('getAppValue')
-			->with('core', 'shareapi_enabled', 'yes')
-			->will($this->returnValue('yes'));
-
-		$this->config
-			->expects($this->at(1))
-			->method('getAppValue')
-			->with('core', 'shareapi_allow_links', 'yes')
-			->will($this->returnValue('yes'));
-
-		$this->assertTrue(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled'));
-	}
-
-
-	public function testIsLinkSharingEnabledWithLinkSharingDisabled() {
-		$this->config
-			->expects($this->at(0))
-			->method('getAppValue')
-			->with('core', 'shareapi_enabled', 'yes')
-			->will($this->returnValue('yes'));
-
-		$this->config
-			->expects($this->at(1))
-			->method('getAppValue')
-			->with('core', 'shareapi_allow_links', 'yes')
-			->will($this->returnValue('no'));
-
-		$this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled'));
-	}
-
-	public function testIsLinkSharingEnabledWithSharingAPIDisabled() {
-		$this->config
-			->expects($this->once())
-			->method('getAppValue')
-			->with('core', 'shareapi_enabled', 'yes')
-			->will($this->returnValue('no'));
-
-		$this->assertFalse(self::invokePrivate($this->sharingCheckMiddleware, 'isLinkSharingEnabled'));
-	}
-
 	public function externalSharesChecksDataProvider() {
 
 		$data = [];
@@ -236,57 +193,11 @@ class SharingCheckMiddlewareTest extends \Test\TestCase {
 			->with('files_sharing')
 			->will($this->returnValue(true));
 
-		$this->config
-			->expects($this->at(0))
-			->method('getAppValue')
-			->with('core', 'shareapi_enabled', 'yes')
-			->will($this->returnValue('yes'));
-
-		$this->config
-			->expects($this->at(1))
-			->method('getAppValue')
-			->with('core', 'shareapi_allow_links', 'yes')
-			->will($this->returnValue('yes'));
-
-		$this->request->expects($this->once())->method('getParam')->with('token')
-			->willReturn('token');
-		$this->shareManager->expects($this->once())->method('getShareByToken')
-			->with('token')->willReturn($share);
-
-		$share->expects($this->once())->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK);
-
 		$controller = $this->createMock(ShareController::class);
 
 		$this->sharingCheckMiddleware->beforeController($controller, 'myMethod');
 	}
 
-	/**
-	 * @expectedException \OCP\Files\NotFoundException
-	 * @expectedExceptionMessage Link sharing is disabled
-	 */
-	public function testBeforeControllerWithShareControllerWithSharingEnabledAPIDisabled() {
-
-		$share = $this->createMock(IShare::class);
-
-		$this->appManager
-			->expects($this->once())
-			->method('isEnabledForUser')
-			->with('files_sharing')
-			->will($this->returnValue(true));
-
-		$controller = $this->createMock(ShareController::class);
-
-		$this->request->expects($this->once())->method('getParam')->with('token')
-			->willReturn('token');
-		$this->shareManager->expects($this->once())->method('getShareByToken')
-			->with('token')->willReturn($share);
-
-		$share->expects($this->once())->method('getShareType')->willReturn(\OCP\Share::SHARE_TYPE_LINK);
-
-
-		$this->sharingCheckMiddleware->beforeController($controller, 'myMethod');
-	}
-
 	/**
 	 * @expectedException \OCP\Files\NotFoundException
 	 * @expectedExceptionMessage Sharing is disabled.
diff --git a/apps/files_sharing/css/authenticate.css b/core/css/publicshareauth.css
similarity index 100%
rename from apps/files_sharing/css/authenticate.css
rename to core/css/publicshareauth.css
diff --git a/apps/files_sharing/js/authenticate.js b/core/js/publicshareauth.js
similarity index 100%
rename from apps/files_sharing/js/authenticate.js
rename to core/js/publicshareauth.js
diff --git a/apps/files_sharing/templates/authenticate.php b/core/templates/publicshareauth.php
similarity index 91%
rename from apps/files_sharing/templates/authenticate.php
rename to core/templates/publicshareauth.php
index 6f270c2851ae2eb825a0293fa65882e6f37a0026..adcc2853f873fd2a2d394793c2d4a748be6c1d67 100644
--- a/apps/files_sharing/templates/authenticate.php
+++ b/core/templates/publicshareauth.php
@@ -2,8 +2,8 @@
 	/** @var $_ array */
 	/** @var $l \OCP\IL10N */
 	style('core', 'guest');
-	style('files_sharing', 'authenticate');
-	script('files_sharing', 'authenticate'); 
+	style('core', 'publicshareauth');
+	script('core', 'publicshareauth');
 ?>
 <form method="post">
 	<fieldset class="warning">
diff --git a/lib/composer/composer/autoload_classmap.php b/lib/composer/composer/autoload_classmap.php
index 777298866012f16d3aba4d930919c622f596b8bd..2426272886b4a64108829cde035046691ee799b1 100644
--- a/lib/composer/composer/autoload_classmap.php
+++ b/lib/composer/composer/autoload_classmap.php
@@ -18,6 +18,7 @@ return array(
     'OCP\\App' => $baseDir . '/lib/public/App.php',
     'OCP\\AppFramework\\ApiController' => $baseDir . '/lib/public/AppFramework/ApiController.php',
     'OCP\\AppFramework\\App' => $baseDir . '/lib/public/AppFramework/App.php',
+    'OCP\\AppFramework\\AuthPublicShareController' => $baseDir . '/lib/public/AppFramework/AuthPublicShareController.php',
     'OCP\\AppFramework\\Controller' => $baseDir . '/lib/public/AppFramework/Controller.php',
     'OCP\\AppFramework\\Db\\DoesNotExistException' => $baseDir . '/lib/public/AppFramework/Db/DoesNotExistException.php',
     'OCP\\AppFramework\\Db\\Entity' => $baseDir . '/lib/public/AppFramework/Db/Entity.php',
@@ -56,6 +57,7 @@ return array(
     'OCP\\AppFramework\\OCS\\OCSException' => $baseDir . '/lib/public/AppFramework/OCS/OCSException.php',
     'OCP\\AppFramework\\OCS\\OCSForbiddenException' => $baseDir . '/lib/public/AppFramework/OCS/OCSForbiddenException.php',
     'OCP\\AppFramework\\OCS\\OCSNotFoundException' => $baseDir . '/lib/public/AppFramework/OCS/OCSNotFoundException.php',
+    'OCP\\AppFramework\\PublicShareController' => $baseDir . '/lib/public/AppFramework/PublicShareController.php',
     'OCP\\AppFramework\\QueryException' => $baseDir . '/lib/public/AppFramework/QueryException.php',
     'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => $baseDir . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php',
     'OCP\\AppFramework\\Utility\\ITimeFactory' => $baseDir . '/lib/public/AppFramework/Utility/ITimeFactory.php',
@@ -350,6 +352,8 @@ return array(
     'OC\\AppFramework\\Http\\Request' => $baseDir . '/lib/private/AppFramework/Http/Request.php',
     'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => $baseDir . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php',
     'OC\\AppFramework\\Middleware\\OCSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/OCSMiddleware.php',
+    'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php',
+    'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php',
     'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php',
     'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => $baseDir . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php',
     'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => $baseDir . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php',
diff --git a/lib/composer/composer/autoload_static.php b/lib/composer/composer/autoload_static.php
index be9c71d824649cc7b134ee13c9f0c955178b0252..26a38a29984fd59b368d997091ec94f6c5694947 100644
--- a/lib/composer/composer/autoload_static.php
+++ b/lib/composer/composer/autoload_static.php
@@ -48,6 +48,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\App' => __DIR__ . '/../../..' . '/lib/public/App.php',
         'OCP\\AppFramework\\ApiController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/ApiController.php',
         'OCP\\AppFramework\\App' => __DIR__ . '/../../..' . '/lib/public/AppFramework/App.php',
+        'OCP\\AppFramework\\AuthPublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/AuthPublicShareController.php',
         'OCP\\AppFramework\\Controller' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Controller.php',
         'OCP\\AppFramework\\Db\\DoesNotExistException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/DoesNotExistException.php',
         'OCP\\AppFramework\\Db\\Entity' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Db/Entity.php',
@@ -86,6 +87,7 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OCP\\AppFramework\\OCS\\OCSException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSException.php',
         'OCP\\AppFramework\\OCS\\OCSForbiddenException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSForbiddenException.php',
         'OCP\\AppFramework\\OCS\\OCSNotFoundException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/OCS/OCSNotFoundException.php',
+        'OCP\\AppFramework\\PublicShareController' => __DIR__ . '/../../..' . '/lib/public/AppFramework/PublicShareController.php',
         'OCP\\AppFramework\\QueryException' => __DIR__ . '/../../..' . '/lib/public/AppFramework/QueryException.php',
         'OCP\\AppFramework\\Utility\\IControllerMethodReflector' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/IControllerMethodReflector.php',
         'OCP\\AppFramework\\Utility\\ITimeFactory' => __DIR__ . '/../../..' . '/lib/public/AppFramework/Utility/ITimeFactory.php',
@@ -380,6 +382,8 @@ class ComposerStaticInit53792487c5a8370acc0b06b1a864ff4c
         'OC\\AppFramework\\Http\\Request' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Http/Request.php',
         'OC\\AppFramework\\Middleware\\MiddlewareDispatcher' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/MiddlewareDispatcher.php',
         'OC\\AppFramework\\Middleware\\OCSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/OCSMiddleware.php',
+        'OC\\AppFramework\\Middleware\\PublicShare\\Exceptions\\NeedAuthenticationException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php',
+        'OC\\AppFramework\\Middleware\\PublicShare\\PublicShareMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php',
         'OC\\AppFramework\\Middleware\\Security\\BruteForceMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/BruteForceMiddleware.php',
         'OC\\AppFramework\\Middleware\\Security\\CORSMiddleware' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/CORSMiddleware.php',
         'OC\\AppFramework\\Middleware\\Security\\Exceptions\\AppNotEnabledException' => __DIR__ . '/../../..' . '/lib/private/AppFramework/Middleware/Security/Exceptions/AppNotEnabledException.php',
diff --git a/lib/private/AppFramework/DependencyInjection/DIContainer.php b/lib/private/AppFramework/DependencyInjection/DIContainer.php
index c82ac5255dd409744cebd17622112b75b4c6bbec..8803ef8c47dba1a47769537cbfc8a5e32e32d55d 100644
--- a/lib/private/AppFramework/DependencyInjection/DIContainer.php
+++ b/lib/private/AppFramework/DependencyInjection/DIContainer.php
@@ -62,6 +62,7 @@ use OCP\IL10N;
 use OCP\ILogger;
 use OCP\IRequest;
 use OCP\IServerContainer;
+use OCP\ISession;
 use OCP\IUserSession;
 use OCP\RichObjectStrings\IValidator;
 use OCP\Encryption\IManager;
@@ -304,7 +305,7 @@ class DIContainer extends SimpleContainer implements IAppContainer {
 		});
 
 		$middleWares = &$this->middleWares;
-		$this->registerService('MiddlewareDispatcher', function($c) use (&$middleWares) {
+		$this->registerService('MiddlewareDispatcher', function(SimpleContainer $c) use (&$middleWares) {
 			$dispatcher = new MiddlewareDispatcher();
 			$dispatcher->registerMiddleware($c[OC\AppFramework\Middleware\Security\SameSiteCookieMiddleware::class]);
 			$dispatcher->registerMiddleware($c['CORSMiddleware']);
@@ -314,6 +315,11 @@ class DIContainer extends SimpleContainer implements IAppContainer {
 			$dispatcher->registerMiddleware($c['TwoFactorMiddleware']);
 			$dispatcher->registerMiddleware($c['BruteForceMiddleware']);
 			$dispatcher->registerMiddleware($c['RateLimitingMiddleware']);
+			$dispatcher->registerMiddleware(new OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware(
+				$c['Request'],
+				$c->query(ISession::class),
+				$c->query(\OCP\IConfig::class)
+			));
 
 			foreach($middleWares as $middleWare) {
 				$dispatcher->registerMiddleware($c[$middleWare]);
diff --git a/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
new file mode 100644
index 0000000000000000000000000000000000000000..27e57fe9505a208bc11b551d34c633a6a5153bbb
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/PublicShare/Exceptions/NeedAuthenticationException.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace OC\AppFramework\Middleware\PublicShare\Exceptions;
+
+class NeedAuthenticationException extends \Exception {
+
+}
diff --git a/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
new file mode 100644
index 0000000000000000000000000000000000000000..05783b21900df8146c52a4266b00ecca8eb14d26
--- /dev/null
+++ b/lib/private/AppFramework/Middleware/PublicShare/PublicShareMiddleware.php
@@ -0,0 +1,112 @@
+<?php
+
+namespace OC\AppFramework\Middleware\PublicShare;
+
+use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OCP\AppFramework\AuthPublicShareController;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\Response;
+use OCP\AppFramework\Middleware;
+use OCP\AppFramework\PublicShareController;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+
+class PublicShareMiddleware extends Middleware {
+	/** @var IRequest */
+	private $request;
+
+	/** @var ISession */
+	private $session;
+
+	/** @var IConfig */
+	private $config;
+
+	public function __construct(IRequest $request, ISession $session, IConfig $config) {
+		$this->request = $request;
+		$this->session = $session;
+		$this->config = $config;
+	}
+
+	public function beforeController($controller, $methodName) {
+		if (!($controller instanceof PublicShareController)) {
+			return;
+		}
+
+		if (!$this->isLinkSharingEnabled()) {
+			throw new NotFoundException('Link sharing is disabled');
+		}
+
+		// We require the token parameter to be set
+		$token = $this->request->getParam('token');
+		if ($token === null) {
+			throw new NotFoundException();
+		}
+
+		// Set the token
+		$controller->setToken($token);
+
+		if (!$controller->isValidToken()) {
+			$controller->shareNotFound();
+			throw new NotFoundException();
+		}
+
+		// No need to check for authentication when we try to authenticate
+		if ($methodName === 'authenticate' || $methodName === 'showAuthenticate') {
+			return;
+		}
+
+		// If authentication succeeds just continue
+		if ($controller->isAuthenticated()) {
+			return;
+		}
+
+		// If we can authenticate to this controller do it else we throw a 404 to not leak any info
+		if ($controller instanceof AuthPublicShareController) {
+			$this->session->set('public_link_authenticate_redirect', json_encode($this->request->getParams()));
+			throw new NeedAuthenticationException();
+		}
+
+		throw new NotFoundException();
+
+	}
+
+	public function afterException($controller, $methodName, \Exception $exception) {
+		if (!($controller instanceof PublicShareController)) {
+			throw $exception;
+		}
+
+		if ($exception instanceof NotFoundException) {
+			return new NotFoundResponse();
+		}
+
+		if ($controller instanceof AuthPublicShareController && $exception instanceof NeedAuthenticationException) {
+			return $controller->getAuthenticationRedirect($this->getFunctionForRoute($this->request->getParam('_route')));
+		}
+
+		throw $exception;
+	}
+
+	private function getFunctionForRoute(string $route): string {
+		$tmp = explode('.', $route);
+		return array_pop($tmp);
+	}
+
+	/**
+	 * Check if link sharing is allowed
+	 */
+	private function isLinkSharingEnabled(): bool {
+		// Check if the shareAPI is enabled
+		if ($this->config->getAppValue('core', 'shareapi_enabled', 'yes') !== 'yes') {
+			return false;
+		}
+
+		// Check whether public sharing is enabled
+		if($this->config->getAppValue('core', 'shareapi_allow_links', 'yes') !== 'yes') {
+			return false;
+		}
+
+		return true;
+	}
+}
diff --git a/lib/private/legacy/template/functions.php b/lib/private/legacy/template/functions.php
index 290ffe120a3314769b05a7066bfd56680ad3b72d..55d3a5956894e599a0d381272ba2f8dddefaf878 100644
--- a/lib/private/legacy/template/functions.php
+++ b/lib/private/legacy/template/functions.php
@@ -262,7 +262,7 @@ function preview_icon( $path ) {
  * @return string
  */
 function publicPreview_icon ( $path, $token ) {
-	return \OC::$server->getURLGenerator()->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 't' => $token]);
+	return \OC::$server->getURLGenerator()->linkToRoute('files_sharing.PublicPreview.getPreview', ['x' => 32, 'y' => 32, 'file' => $path, 'token' => $token]);
 }
 
 /**
diff --git a/lib/public/AppFramework/AuthPublicShareController.php b/lib/public/AppFramework/AuthPublicShareController.php
new file mode 100644
index 0000000000000000000000000000000000000000..ffd2bddd24bb0797146717114601e2e48d38e46b
--- /dev/null
+++ b/lib/public/AppFramework/AuthPublicShareController.php
@@ -0,0 +1,192 @@
+<?php
+/**
+ * @copyright 2018, 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/>.
+ *
+ */
+declare(strict_types=1);
+
+namespace OCP\AppFramework;
+
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+/**
+ * Base controller for interactive public shares
+ *
+ * It will verify if the user is properly authenticated to the share. If not the
+ * user will be redirected to an authentication page.
+ *
+ * Use this for a controller that is to be called directly by a user. So the
+ * normal public share page for files/calendars etc.
+ *
+ * @since 14.0.0
+ */
+abstract class AuthPublicShareController extends PublicShareController {
+
+	/** @var IURLGenerator */
+	protected $urlGenerator;
+
+	/**
+	 * @since 14.0.0
+	 */
+	public function __construct(string $appName,
+								IRequest $request,
+								ISession $session,
+								IURLGenerator $urlGenerator) {
+		parent::__construct($appName, $request, $session);
+
+		$this->urlGenerator = $urlGenerator;
+	}
+
+	/**
+	 * @PublicPage
+	 * @NoCSRFRequired
+	 *
+	 * Show the authentication page
+	 * The form has to submit to the authenticate method route
+	 *
+	 * @since 14.0.0
+	 */
+	public function showAuthenticate(): TemplateResponse {
+		return new TemplateResponse('core', 'publicshareauth', [], 'guest');
+	}
+
+	/**
+	 * The template to show when authentication failed
+	 *
+	 * @since 14.0.0
+	 */
+	protected function showAuthFailed(): TemplateResponse {
+		return new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest');
+	}
+
+	/**
+	 * Verify the password
+	 *
+	 * @since 14.0.0
+	 */
+	abstract protected function verifyPassword(string $password): bool;
+
+	/**
+	 * Function called after failed authentication
+	 *
+	 * You can use this to do some logging for example
+	 *
+	 * @since 14.0.0
+	 */
+	protected function authFailed() {
+	}
+
+	/**
+	 * Function called after successfull authentication
+	 *
+	 * You can use this to do some logging for example
+	 *
+	 * @since 14.0.0
+	 */
+	protected function authSucceeded() {
+	}
+
+	/**
+	 * @UseSession
+	 * @PublicPage
+	 * @BruteForceProtection(action=publicLinkAuth)
+	 *
+	 * Authenticate the share
+	 *
+	 * @since 14.0.0
+	 */
+	final public function authenticate(string $password = '') {
+		// Already authenticated
+		if ($this->isAuthenticated()) {
+			return $this->getRedirect();
+		}
+
+		if (!$this->verifyPassword($password)) {
+			$this->authFailed();
+			$response = $this->showAuthFailed();
+			$response->throttle();
+			return $response;
+		}
+
+		$this->session->regenerateId(true, true);
+		$response = $this->getRedirect();
+
+		$this->session->set('public_link_authenticated_token', $this->getToken());
+		$this->session->set('public_link_authenticated_password_hash', $this->getPasswordHash());
+
+		$this->authSucceeded();
+
+		return $response;
+	}
+
+	/**
+	 * Default landing page
+	 *
+	 * @since 14.0.0
+	 */
+	abstract public function showShare(): TemplateResponse;
+
+	/**
+	 * @since 14.0.0
+	 */
+	final public function getAuthenticationRedirect(string $redirect): RedirectResponse {
+		return new RedirectResponse(
+			$this->urlGenerator->linkToRoute($this->getRoute('showAuthenticate'), ['token' => $this->getToken(), 'redirect' => $redirect])
+		);
+	}
+
+	/**
+	 * @since 14.0.0
+	 */
+	private function getRoute(string $function): string {
+		$app = strtolower($this->appName);
+		$class = strtolower((new \ReflectionClass($this))->getShortName());
+
+		return $app . '.' . $class . '.' . $function;
+	}
+
+	/**
+	 * @since 14.0.0
+	 */
+	private function getRedirect(): RedirectResponse {
+		//Get all the stored redirect parameters:
+		$params = $this->session->get('public_link_authenticate_redirect');
+
+		$route = $this->getRoute('showShare');
+
+		if ($params === null) {
+			$params = [
+				'token' => $this->getToken(),
+			];
+		} else {
+			$params = json_decode($params, true);
+			if (isset($params['_route'])) {
+				$route = $params['_route'];
+				unset($params['_route']);
+			}
+		}
+
+		return new RedirectResponse($this->urlGenerator->linkToRoute($route, $params));
+	}
+}
diff --git a/lib/public/AppFramework/PublicShareController.php b/lib/public/AppFramework/PublicShareController.php
new file mode 100644
index 0000000000000000000000000000000000000000..d0e54a0295b2a60d700905af8f48194c9aa12fde
--- /dev/null
+++ b/lib/public/AppFramework/PublicShareController.php
@@ -0,0 +1,138 @@
+<?php
+/**
+ * @copyright 2018, 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/>.
+ *
+ */
+declare(strict_types=1);
+
+namespace OCP\AppFramework;
+
+use OCP\IRequest;
+use OCP\ISession;
+
+/**
+ * Base controller for public shares
+ *
+ * It will verify if the user is properly authenticated to the share. If not a 404
+ * is thrown by the PublicShareMiddleware.
+ *
+ * Use this for example for a controller that is not to be called via a webbrowser
+ * directly. For example a PublicPreviewController. As this is not meant to be
+ * called by a user direclty.
+ *
+ * To show an auth page extend the AuthPublicShareController
+ *
+ * @since 14.0.0
+ */
+abstract class PublicShareController extends Controller {
+
+	/** @var ISession */
+	protected $session;
+
+	/** @var string */
+	private $token;
+
+	/**
+	 * @since 14.0.0
+	 */
+	public function __construct(string $appName,
+								IRequest $request,
+								ISession $session) {
+		parent::__construct($appName, $request);
+
+		$this->session = $session;
+	}
+
+	/**
+	 * Middleware set the token for the request
+	 *
+	 * @since 14.0.0
+	 */
+	final public function setToken(string $token) {
+		$this->token = $token;
+	}
+
+	/**
+	 * Get the token for this request
+	 *
+	 * @since 14.0.0
+	 */
+	final public function getToken(): string {
+		return $this->token;
+	}
+
+	/**
+	 * Get a hash of the password for this share
+	 *
+	 * To ensure access is blocked when the password to a share is changed we store
+	 * a hash of the password for this token.
+	 *
+	 * @since 14.0.0
+	 */
+	abstract protected function getPasswordHash(): string;
+
+	/**
+	 * Is the provided token a valid token
+	 *
+	 * This function is already called from the middleware directly after setting the token.
+	 *
+	 * @since 14.0.0
+	 */
+	abstract public function isValidToken(): bool;
+
+	/**
+	 * Is a share with this token password protected
+	 *
+	 * @since 14.0.0
+	 */
+	abstract protected function isPasswordProtected(): bool;
+
+	/**
+	 * Check if a share is authenticated or not
+	 *
+	 * @since 14.0.0
+	 */
+	final public function isAuthenticated(): bool {
+		// Always authenticated against non password protected shares
+		if (!$this->isPasswordProtected()) {
+			return true;
+		}
+
+		// If we are authenticated properly
+		if ($this->session->get('public_link_authenticated_token') === $this->getToken() &&
+			$this->session->get('public_link_authenticated_password_hash') === $this->getPasswordHash()) {
+			return true;
+		}
+
+		// Fail by default if nothing matches
+		return false;
+	}
+
+	/**
+	 * Function called if the share is not found.
+	 *
+	 * You can use this to do some logging for example
+	 *
+	 * @since 14.0.0
+	 */
+	public function shareNotFound() {
+
+	}
+}
diff --git a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
index 61357142ae481a665c825f370ebc3d2a9e9e0cfb..1fe12d5f42dcba98c071a1793d9251bdff42a002 100644
--- a/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
+++ b/tests/acceptance/features/bootstrap/FilesSharingAppContext.php
@@ -137,7 +137,7 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
 	 */
 	public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheSharedLinkIWroteDown() {
 		PHPUnit_Framework_Assert::assertEquals(
-				$this->actor->getSharedNotebook()["shared link"] . "/authenticate/preview",
+				$this->actor->getSharedNotebook()["shared link"] . "/authenticate/showShare",
 				$this->actor->getSession()->getCurrentUrl());
 	}
 
@@ -146,7 +146,7 @@ class FilesSharingAppContext implements Context, ActorAwareInterface {
 	 */
 	public function iSeeThatTheCurrentPageIsTheAuthenticatePageForTheDirectDownloadSharedLinkIWroteDown() {
 		PHPUnit_Framework_Assert::assertEquals(
-				$this->actor->getSharedNotebook()["shared link"] . "/authenticate/download",
+				$this->actor->getSharedNotebook()["shared link"] . "/authenticate/downloadShare",
 				$this->actor->getSession()->getCurrentUrl());
 	}
 
diff --git a/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php b/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..169ec82ce6d9fd42a9eebe85883d9c4e8a5cdcc9
--- /dev/null
+++ b/tests/lib/AppFramework/Controller/AuthPublicShareControllerTest.php
@@ -0,0 +1,159 @@
+<?php
+/**
+ * @copyright 2018, 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 Test\AppFramework\Controller;
+
+use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware;
+use OCP\AppFramework\AuthPublicShareController;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\Http\TemplateResponse;
+use OCP\AppFramework\PublicShareController;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+class AuthPublicShareControllerTest extends \Test\TestCase {
+
+	/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+	private $request;
+	/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
+	private $session;
+	/** @var IURLGenerator|\PHPUnit_Framework_MockObject_MockObject */
+	private $urlGenerator;
+
+	/** @var AuthPublicShareController|\PHPUnit_Framework_MockObject_MockObject */
+	private $controller;
+
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->request = $this->createMock(IRequest::class);
+		$this->session = $this->createMock(ISession::class);
+		$this->urlGenerator = $this->createMock(IURLGenerator::class);
+
+		$this->controller = $this->getMockBuilder(AuthPublicShareController::class)
+			->setConstructorArgs([
+				'app',
+				$this->request,
+				$this->session,
+				$this->urlGenerator
+			])->setMethods([
+				'authFailed',
+				'getPasswordHash',
+				'isAuthenticated',
+				'isPasswordProtected',
+				'isValidToken',
+				'showShare',
+				'verifyPassword'
+			])->getMock();
+	}
+
+	public function testShowAuthenticate() {
+		$expects = new TemplateResponse('core', 'publicshareauth', [], 'guest');
+
+		$this->assertEquals($expects, $this->controller->showAuthenticate());
+	}
+
+	public function testAuthenticateAuthenticated() {
+		$this->controller->method('isAuthenticated')
+			->willReturn(true);
+
+		$this->controller->setToken('myToken');
+
+		$this->session->method('get')
+			->willReturnMap(['public_link_authenticate_redirect', ['foo' => 'bar']]);
+
+		$this->urlGenerator->method('linkToRoute')
+			->willReturn('myLink!');
+
+		$result = $this->controller->authenticate('password');
+		$this->assertInstanceOf(RedirectResponse::class, $result);
+		$this->assertSame('myLink!', $result->getRedirectURL());
+	}
+
+	public function testAuthenticateInvalidPassword() {
+		$this->controller->setToken('token');
+		$this->controller->method('isPasswordProtected')
+			->willReturn(true);
+
+		$this->controller->method('verifyPassword')
+			->with('password')
+			->willReturn(false);
+
+		$this->controller->expects($this->once())
+			->method('authFailed');
+
+		$expects = new TemplateResponse('core', 'publicshareauth', ['wrongpw' => true], 'guest');
+		$expects->throttle();
+
+		$result = $this->controller->authenticate('password');
+
+		$this->assertEquals($expects, $result);
+	}
+
+	public function testAuthenticateValidPassword() {
+		$this->controller->setToken('token');
+		$this->controller->method('isPasswordProtected')
+			->willReturn(true);
+		$this->controller->method('verifyPassword')
+			->with('password')
+			->willReturn(true);
+		$this->controller->method('getPasswordHash')
+			->willReturn('hash');
+
+		$this->session->expects($this->once())
+			->method('regenerateId');
+		$this->session->method('get')
+			->willReturnMap(['public_link_authenticate_redirect', ['foo' => 'bar']]);
+
+		$tokenSet = false;
+		$hashSet = false;
+		$this->session
+			->method('set')
+			->will($this->returnCallback(function($key, $value) use (&$tokenSet, &$hashSet) {
+				if ($key === 'public_link_authenticated_token' && $value === 'token') {
+					$tokenSet = true;
+					return true;
+				}
+				if ($key === 'public_link_authenticated_password_hash' && $value === 'hash') {
+					$hashSet = true;
+					return true;
+				}
+				return false;
+			}));
+
+		$this->urlGenerator->method('linkToRoute')
+			->willReturn('myLink!');
+
+		$result = $this->controller->authenticate('password');
+		$this->assertInstanceOf(RedirectResponse::class, $result);
+		$this->assertSame('myLink!', $result->getRedirectURL());
+		$this->assertTrue($tokenSet);
+		$this->assertTrue($hashSet);
+	}
+}
diff --git a/tests/lib/AppFramework/Controller/PublicShareControllerTest.php b/tests/lib/AppFramework/Controller/PublicShareControllerTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..eff7563cc4f35dc75b9982588f75452966aa22ee
--- /dev/null
+++ b/tests/lib/AppFramework/Controller/PublicShareControllerTest.php
@@ -0,0 +1,102 @@
+<?php
+/**
+ * @copyright 2018, 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 Test\AppFramework\Controller;
+
+use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware;
+use OCP\AppFramework\AuthPublicShareController;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\PublicShareController;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+
+class PublicShareControllerTest extends \Test\TestCase {
+
+	/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+	private $request;
+	/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
+	private $session;
+
+	/** @var PublicShareController|\PHPUnit_Framework_MockObject_MockObject */
+	private $controller;
+
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->request = $this->createMock(IRequest::class);
+		$this->session = $this->createMock(ISession::class);
+
+		$this->controller = $this->getMockBuilder(PublicShareController::class)
+			->setConstructorArgs([
+				'app',
+				$this->request,
+				$this->session
+			])->getMock();
+	}
+
+	public function testGetToken() {
+		$this->controller->setToken('test');
+		$this->assertEquals('test', $this->controller->getToken());
+	}
+
+	public function dataIsAuthenticated() {
+		return [
+			[false, 'token1', 'token1', 'hash1', 'hash1',  true],
+			[false, 'token1', 'token1', 'hash1', 'hash2',  true],
+			[false, 'token1', 'token2', 'hash1', 'hash1',  true],
+			[false, 'token1', 'token2', 'hash1', 'hash2',  true],
+			[ true, 'token1', 'token1', 'hash1', 'hash1',  true],
+			[ true, 'token1', 'token1', 'hash1', 'hash2', false],
+			[ true, 'token1', 'token2', 'hash1', 'hash1', false],
+			[ true, 'token1', 'token2', 'hash1', 'hash2', false],
+		];
+	}
+
+	/**
+	 * @dataProvider dataIsAuthenticated
+	 */
+	public function testIsAuthenticatedNotPasswordProtected(bool $protected, string $token1, string $token2, string $hash1, string $hash2, bool $expected) {
+		$this->controller->method('isPasswordProtected')
+			->willReturn($protected);
+
+		$this->session->method('get')
+			->willReturnMap([
+				['public_link_authenticated_token', $token1],
+				['public_link_authenticated_password_hash', $hash1],
+			]);
+
+		$this->controller->setToken($token2);
+		$this->controller->method('getPasswordHash')
+			->willReturn($hash2);
+
+		$this->assertEquals($expected, $this->controller->isAuthenticated());
+	}
+
+}
diff --git a/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php b/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php
new file mode 100644
index 0000000000000000000000000000000000000000..de610100c2adcb2a7e7f90cb8d30afc7d2e3f55d
--- /dev/null
+++ b/tests/lib/AppFramework/Middleware/PublicShare/PublicShareMiddlewareTest.php
@@ -0,0 +1,287 @@
+<?php
+/**
+ * @copyright 2018, 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 Test\AppFramework\Middleware\PublicShare;
+
+use OC\AppFramework\Middleware\PublicShare\Exceptions\NeedAuthenticationException;
+use OC\AppFramework\Middleware\PublicShare\PublicShareMiddleware;
+use OCP\AppFramework\AuthPublicShareController;
+use OCP\AppFramework\Controller;
+use OCP\AppFramework\Http\NotFoundResponse;
+use OCP\AppFramework\Http\RedirectResponse;
+use OCP\AppFramework\PublicShareController;
+use OCP\Files\NotFoundException;
+use OCP\IConfig;
+use OCP\IRequest;
+use OCP\ISession;
+use OCP\IURLGenerator;
+
+
+class PublicShareMiddlewareTest extends \Test\TestCase {
+
+	/** @var IRequest|\PHPUnit_Framework_MockObject_MockObject */
+	private $request;
+	/** @var ISession|\PHPUnit_Framework_MockObject_MockObject */
+	private $session;
+	/** @var IConfig|\PHPUnit_Framework_MockObject_MockObject */
+	private $config;
+
+	/** @var PublicShareMiddleware */
+	private $middleware;
+
+
+	protected function setUp() {
+		parent::setUp();
+
+		$this->request = $this->createMock(IRequest::class);
+		$this->session = $this->createMock(ISession::class);
+		$this->config = $this->createMock(IConfig::class);
+
+		$this->middleware = new PublicShareMiddleware(
+			$this->request,
+			$this->session,
+			$this->config
+		);
+	}
+
+	public function testBeforeControllerNoPublicShareController() {
+		$controller = $this->createMock(Controller::class);
+
+		$this->middleware->beforeController($controller, 'method');
+		$this->assertTrue(true);
+	}
+
+	public function dataShareApi() {
+		return [
+			['no', 'no',],
+			['no', 'yes',],
+			['yes', 'no',],
+		];
+	}
+
+	/**
+	 * @dataProvider dataShareApi
+	 */
+	public function testBeforeControllerShareApiDisabled(string $shareApi, string $shareLinks) {
+		$controller = $this->createMock(PublicShareController::class);
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', $shareApi],
+				['core', 'shareapi_allow_links', 'yes', $shareLinks],
+			]);
+
+		$this->expectException(NotFoundException::class);
+		$this->middleware->beforeController($controller, 'mehod');
+	}
+
+	public function testBeforeControllerNoTokenParam() {
+		$controller = $this->createMock(PublicShareController::class);
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->expectException(NotFoundException::class);
+		$this->middleware->beforeController($controller, 'mehod');
+	}
+
+	public function testBeforeControllerInvalidToken() {
+		$controller = $this->createMock(PublicShareController::class);
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->request->method('getParam')
+			->with('token', null)
+			->willReturn('myToken');
+
+		$controller->method('isValidToken')
+			->willReturn(false);
+		$controller->expects($this->once())
+			->method('shareNotFound');
+
+		$this->expectException(NotFoundException::class);
+		$this->middleware->beforeController($controller, 'mehod');
+	}
+
+	public function testBeforeControllerValidTokenNotAuthenticated() {
+		$controller = $this->getMockBuilder(PublicShareController::class)
+			->setConstructorArgs(['app', $this->request, $this->session])
+			->getMock();
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->request->method('getParam')
+			->with('token', null)
+			->willReturn('myToken');
+
+		$controller->method('isValidToken')
+			->willReturn(true);
+
+		$controller->method('isPasswordProtected')
+			->willReturn(true);
+
+		$this->expectException(NotFoundException::class);
+		$this->middleware->beforeController($controller, 'mehod');
+	}
+
+	public function testBeforeControllerValidTokenAuthenticateMethod() {
+		$controller = $this->getMockBuilder(PublicShareController::class)
+			->setConstructorArgs(['app', $this->request, $this->session])
+			->getMock();
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->request->method('getParam')
+			->with('token', null)
+			->willReturn('myToken');
+
+		$controller->method('isValidToken')
+			->willReturn(true);
+
+		$controller->method('isPasswordProtected')
+			->willReturn(true);
+
+		$this->middleware->beforeController($controller, 'authenticate');
+		$this->assertTrue(true);
+	}
+
+	public function testBeforeControllerValidTokenShowAuthenticateMethod() {
+		$controller = $this->getMockBuilder(PublicShareController::class)
+			->setConstructorArgs(['app', $this->request, $this->session])
+			->getMock();
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->request->method('getParam')
+			->with('token', null)
+			->willReturn('myToken');
+
+		$controller->method('isValidToken')
+			->willReturn(true);
+
+		$controller->method('isPasswordProtected')
+			->willReturn(true);
+
+		$this->middleware->beforeController($controller, 'showAuthenticate');
+		$this->assertTrue(true);
+	}
+
+	public function testBeforeControllerAuthPublicShareController() {
+		$controller = $this->getMockBuilder(AuthPublicShareController::class)
+			->setConstructorArgs(['app', $this->request, $this->session, $this->createMock(IURLGenerator::class)])
+			->getMock();
+
+		$this->config->method('getAppValue')
+			->willReturnMap([
+				['core', 'shareapi_enabled', 'yes', 'yes'],
+				['core', 'shareapi_allow_links', 'yes', 'yes'],
+			]);
+
+		$this->request->method('getParam')
+			->with('token', null)
+			->willReturn('myToken');
+
+		$controller->method('isValidToken')
+			->willReturn(true);
+
+		$controller->method('isPasswordProtected')
+			->willReturn(true);
+
+		$this->session->expects($this->once())
+			->method('set')
+			->with('public_link_authenticate_redirect', '[]');
+
+		$this->expectException(NeedAuthenticationException::class);
+		$this->middleware->beforeController($controller, 'method');
+	}
+
+	public function testAfterExceptionNoPublicShareController() {
+		$controller = $this->createMock(Controller::class);
+		$exception = new \Exception();
+
+		try {
+			$this->middleware->afterException($controller, 'method', $exception);
+		} catch (\Exception $e) {
+			$this->assertEquals($exception, $e);
+		}
+	}
+
+	public function testAfterExceptionPublicShareControllerNotFoundException() {
+		$controller = $this->createMock(PublicShareController::class);
+		$exception = new NotFoundException();
+
+		$result = $this->middleware->afterException($controller, 'method', $exception);
+		$this->assertInstanceOf(NotFoundResponse::class, $result);
+	}
+
+	public function testAfterExceptionPublicShareController() {
+		$controller = $this->createMock(PublicShareController::class);
+		$exception = new \Exception();
+
+		try {
+			$this->middleware->afterException($controller, 'method', $exception);
+		} catch (\Exception $e) {
+			$this->assertEquals($exception, $e);
+		}
+	}
+
+	public function testAfterExceptionAuthPublicShareController() {
+		$controller = $this->getMockBuilder(AuthPublicShareController::class)
+			->setConstructorArgs([
+				'app',
+				$this->request,
+				$this->session,
+				$this->createMock(IURLGenerator::class),
+			])->getMock();
+		$controller->setToken('token');
+
+		$exception = new NeedAuthenticationException();
+
+		$this->request->method('getParam')
+			->with('_route')
+			->willReturn('my.route');
+
+		$result = $this->middleware->afterException($controller, 'method', $exception);
+		$this->assertInstanceOf(RedirectResponse::class, $result);
+	}
+
+
+}