diff --git a/lib/private/Authentication/Token/PublicKeyToken.php b/lib/private/Authentication/Token/PublicKeyToken.php
index 9896915ca33ef961c032751aa1fb607ece518730..b6f551467073a42e10ea1ee2ce2a4b8d525030e3 100644
--- a/lib/private/Authentication/Token/PublicKeyToken.php
+++ b/lib/private/Authentication/Token/PublicKeyToken.php
@@ -44,7 +44,6 @@ use OCP\AppFramework\Db\Entity;
  * @method void setPublicKey(string $key)
  * @method void setVersion(int $version)
  * @method bool getPasswordInvalid()
- * @method void setPasswordInvalid(bool $invalid);
  */
 class PublicKeyToken extends Entity implements IToken {
 
@@ -220,4 +219,8 @@ class PublicKeyToken extends Entity implements IToken {
 	public function getExpires() {
 		return parent::getExpires();
 	}
+
+	public function setPasswordInvalid(bool $invalid) {
+		parent::setPasswordInvalid($invalid);
+	}
 }
diff --git a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
index 3fb11f410bac30162fcae837ccf655af04eee0d0..8b005bd8bdbf5443720068f3520445281507ac89 100644
--- a/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
+++ b/tests/lib/Authentication/Token/DefaultTokenProviderTest.php
@@ -28,6 +28,7 @@ use OC\Authentication\Token\DefaultTokenMapper;
 use OC\Authentication\Token\DefaultTokenProvider;
 use OC\Authentication\Token\ExpiredTokenException;
 use OC\Authentication\Token\IToken;
+use OC\Authentication\Token\PublicKeyToken;
 use OCP\AppFramework\Db\DoesNotExistException;
 use OCP\AppFramework\Utility\ITimeFactory;
 use OCP\IConfig;
@@ -532,4 +533,22 @@ class DefaultTokenProviderTest extends TestCase {
 
 		$this->tokenProvider->rotate($token, 'oldtoken', 'newtoken');
 	}
+
+	public function testMarkPasswordInvalidInvalidToken() {
+		$token = $this->createMock(PublicKeyToken::class);
+
+		$this->expectException(InvalidTokenException::class);
+
+		$this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+	}
+
+	public function testMarkPasswordInvalid() {
+		$token = $this->createMock(DefaultToken::class);
+
+		$this->mapper->expects($this->once())
+			->method('invalidate')
+			->with('0c7db0098fe8ddba6032b22719ec18867c69a1820fa36d71c28bf96d52843bdc44a112bd24093b049be5bb54769bcb72d67190a4a9690e51aac263cba38186fb');
+
+		$this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+	}
 }
diff --git a/tests/lib/Authentication/Token/ManagerTest.php b/tests/lib/Authentication/Token/ManagerTest.php
index 8b77bfc499437bfd35468575ed646645883fea9e..1b7f3a637ced258203d522deb13d43988ec9a3bf 100644
--- a/tests/lib/Authentication/Token/ManagerTest.php
+++ b/tests/lib/Authentication/Token/ManagerTest.php
@@ -448,4 +448,45 @@ class ManagerTest extends TestCase {
 
 		$this->assertSame($newToken, $this->manager->rotate($oldToken, 'oldId', 'newId'));
 	}
+
+	public function testMarkPasswordInvalidDefault() {
+		$token = $this->createMock(DefaultToken::class);
+
+		$this->defaultTokenProvider->expects($this->once())
+			->method('markPasswordInvalid')
+			->with($token, 'tokenId');
+		$this->publicKeyTokenProvider->expects($this->never())
+			->method($this->anything());
+
+		$this->manager->markPasswordInvalid($token, 'tokenId');
+	}
+
+	public function testMarkPasswordInvalidPublicKey() {
+		$token = $this->createMock(PublicKeyToken::class);
+
+		$this->publicKeyTokenProvider->expects($this->once())
+			->method('markPasswordInvalid')
+			->with($token, 'tokenId');
+		$this->defaultTokenProvider->expects($this->never())
+			->method($this->anything());
+
+		$this->manager->markPasswordInvalid($token, 'tokenId');
+	}
+
+	public function testMarkPasswordInvalidInvalidToken() {
+		$this->expectException(InvalidTokenException::class);
+
+		$this->manager->markPasswordInvalid($this->createMock(IToken::class), 'tokenId');
+	}
+
+	public function testUpdatePasswords() {
+		$this->defaultTokenProvider->expects($this->once())
+			->method('updatePasswords')
+			->with('uid', 'pass');
+		$this->publicKeyTokenProvider->expects($this->once())
+			->method('updatePasswords')
+			->with('uid', 'pass');
+
+		$this->manager->updatePasswords('uid', 'pass');
+	}
 }
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
index 5a98747ab0d4b1b76df40214cfa167a8c0d5eac1..89adef5bb8f09162fcbfb5fa7240990aab7b6541 100644
--- a/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
+++ b/tests/lib/Authentication/Token/PublicKeyTokenMapperTest.php
@@ -99,6 +99,20 @@ class PublicKeyTokenMapperTest extends TestCase {
 			'private_key' => $qb->createNamedParameter('private key'),
 			'version' => $qb->createNamedParameter(2),
 		])->execute();
+		$qb->insert('authtoken')->values([
+			'uid' => $qb->createNamedParameter('user3'),
+			'login_name' => $qb->createNamedParameter('User3'),
+			'password' => $qb->createNamedParameter('063de945d6f6b26862d9b6f40652f2d5|DZ/z520tfdXPtd0T|395f6b89be8d9d605e409e20b9d9abe477fde1be38a3223f9e508f979bf906e50d9eaa4dca983ca4fb22a241eb696c3f98654e7775f78c4caf13108f98642b53'),
+			'name' => $qb->createNamedParameter('Iceweasel on Linux'),
+			'token' => $qb->createNamedParameter('6d9a290d239d09f2cc33a03cc54cccd46f7dc71630dcc27d39214824bd3e093f1feb4e2b55eb159d204caa15dee9556c202a5aa0b9d67806c3f4ec2cde11af67'),
+			'type' => $qb->createNamedParameter(IToken::TEMPORARY_TOKEN),
+			'last_activity' => $qb->createNamedParameter($this->time - 120, IQueryBuilder::PARAM_INT), // Two minutes ago
+			'last_check' => $this->time - 60 * 10, // 10mins ago
+			'public_key' => $qb->createNamedParameter('public key'),
+			'private_key' => $qb->createNamedParameter('private key'),
+			'version' => $qb->createNamedParameter(2),
+			'password_invalid' => $qb->createNamedParameter(1),
+		])->execute();
 	}
 
 	private function getNumberOfTokens() {
@@ -115,7 +129,7 @@ class PublicKeyTokenMapperTest extends TestCase {
 
 		$this->mapper->invalidate($token);
 
-		$this->assertSame(2, $this->getNumberOfTokens());
+		$this->assertSame(3, $this->getNumberOfTokens());
 	}
 
 	public function testInvalidateInvalid() {
@@ -123,7 +137,7 @@ class PublicKeyTokenMapperTest extends TestCase {
 
 		$this->mapper->invalidate($token);
 
-		$this->assertSame(3, $this->getNumberOfTokens());
+		$this->assertSame(4, $this->getNumberOfTokens());
 	}
 
 	public function testInvalidateOld() {
@@ -131,7 +145,7 @@ class PublicKeyTokenMapperTest extends TestCase {
 
 		$this->mapper->invalidateOld($olderThan);
 
-		$this->assertSame(2, $this->getNumberOfTokens());
+		$this->assertSame(3, $this->getNumberOfTokens());
 	}
 
 	public function testGetToken() {
@@ -224,7 +238,7 @@ class PublicKeyTokenMapperTest extends TestCase {
 		$id = $result->fetch()['id'];
 
 		$this->mapper->deleteById('user1', (int)$id);
-		$this->assertEquals(2, $this->getNumberOfTokens());
+		$this->assertEquals(3, $this->getNumberOfTokens());
 	}
 
 	public function testDeleteByIdWrongUser() {
@@ -233,7 +247,7 @@ class PublicKeyTokenMapperTest extends TestCase {
 		$id = 33;
 
 		$this->mapper->deleteById('user1000', $id);
-		$this->assertEquals(3, $this->getNumberOfTokens());
+		$this->assertEquals(4, $this->getNumberOfTokens());
 	}
 
 	public function testDeleteByName() {
@@ -244,7 +258,12 @@ class PublicKeyTokenMapperTest extends TestCase {
 		$result = $qb->execute();
 		$name = $result->fetch()['name'];
 		$this->mapper->deleteByName($name);
-		$this->assertEquals(2, $this->getNumberOfTokens());
+		$this->assertEquals(3, $this->getNumberOfTokens());
+	}
+
+	public function testHasExpiredTokens() {
+		$this->assertFalse($this->mapper->hasExpiredTokens('user1'));
+		$this->assertTrue($this->mapper->hasExpiredTokens('user3'));
 	}
 
 }
diff --git a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
index 681ad35c644057a03bf99d8cd16722c0976aaa60..02ec62d3d778ffbc5c4f6f49aaa6895338338293 100644
--- a/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
+++ b/tests/lib/Authentication/Token/PublicKeyTokenProviderTest.php
@@ -504,4 +504,76 @@ class PublicKeyTokenProviderTest extends TestCase {
 		$this->assertSame(IToken::REMEMBER, $newToken->getRemember());
 		$this->assertSame(IToken::PERMANENT_TOKEN, $newToken->getType());
 	}
+
+	public function testMarkPasswordInvalidInvalidToken() {
+		$token = $this->createMock(DefaultToken::class);
+
+		$this->expectException(InvalidTokenException::class);
+
+		$this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+	}
+
+	public function testMarkPasswordInvalid() {
+		$token = $this->createMock(PublicKeyToken::class);
+
+		$token->expects($this->once())
+			->method('setPasswordInvalid')
+			->with(true);
+		$this->mapper->expects($this->once())
+			->method('update')
+			->with($token);
+
+		$this->tokenProvider->markPasswordInvalid($token, 'tokenId');
+	}
+
+	public function testUpdatePasswords() {
+		$uid = 'myUID';
+		$token1 = $this->tokenProvider->generateToken(
+			'foo',
+			$uid,
+			$uid,
+			'bar',
+			'random1',
+			IToken::PERMANENT_TOKEN,
+			IToken::REMEMBER);
+		$token2 = $this->tokenProvider->generateToken(
+			'foobar',
+			$uid,
+			$uid,
+			'bar',
+			'random2',
+			IToken::PERMANENT_TOKEN,
+			IToken::REMEMBER);
+
+		$this->mapper->expects($this->once())
+			->method('hasExpiredTokens')
+			->with($uid)
+			->willReturn(true);
+		$this->mapper->expects($this->once())
+			->method('getTokenByUser')
+			->with($uid)
+			->willReturn([$token1, $token2]);
+		$this->mapper->expects($this->exactly(2))
+			->method('update')
+			->with($this->callback(function (PublicKeyToken $t) use ($token1, $token2) {
+				return $t === $token1 || $t === $token2;
+			}));
+
+		$this->tokenProvider->updatePasswords($uid, 'bar2');
+	}
+
+	public function testUpdatePasswordsNotRequired() {
+		$uid = 'myUID';
+
+		$this->mapper->expects($this->once())
+			->method('hasExpiredTokens')
+			->with($uid)
+			->willReturn(false);
+		$this->mapper->expects($this->never())
+			->method('getTokenByUser');
+		$this->mapper->expects($this->never())
+			->method('update');
+
+		$this->tokenProvider->updatePasswords($uid, 'bar2');
+	}
 }
diff --git a/tests/lib/User/SessionTest.php b/tests/lib/User/SessionTest.php
index 81ceade9e02c41da5ad4291aef7190eb104f89eb..114b2eb559764e0b942369cf5571a6d6b1919a24 100644
--- a/tests/lib/User/SessionTest.php
+++ b/tests/lib/User/SessionTest.php
@@ -1067,6 +1067,55 @@ class SessionTest extends \Test\TestCase {
 		$this->assertEquals(1000, $token->getLastCheck());
 	}
 
+	public function testValidateSessionInvalidPassword() {
+		$userManager = $this->createMock(Manager::class);
+		$session = $this->createMock(ISession::class);
+		$timeFactory = $this->createMock(ITimeFactory::class);
+		$tokenProvider = $this->createMock(IProvider::class);
+		$userSession = $this->getMockBuilder(Session::class)
+			->setConstructorArgs([$userManager, $session, $timeFactory, $tokenProvider, $this->config, $this->random, $this->lockdownManager, $this->logger])
+			->setMethods(['logout'])
+			->getMock();
+
+		$user = $this->createMock(IUser::class);
+		$token = new \OC\Authentication\Token\DefaultToken();
+		$token->setLoginName('susan');
+		$token->setLastCheck(20);
+
+		$session->expects($this->once())
+			->method('get')
+			->with('app_password')
+			->will($this->returnValue('APP-PASSWORD'));
+		$tokenProvider->expects($this->once())
+			->method('getToken')
+			->with('APP-PASSWORD')
+			->will($this->returnValue($token));
+		$timeFactory->expects($this->once())
+			->method('getTime')
+			->will($this->returnValue(1000)); // more than 5min since last check
+		$tokenProvider->expects($this->once())
+			->method('getPassword')
+			->with($token, 'APP-PASSWORD')
+			->will($this->returnValue('123456'));
+		$userManager->expects($this->once())
+			->method('checkPassword')
+			->with('susan', '123456')
+			->willReturn(false);
+		$user->expects($this->once())
+			->method('isEnabled')
+			->will($this->returnValue(true));
+		$tokenProvider->expects($this->never())
+			->method('invalidateToken');
+		$tokenProvider->expects($this->once())
+			->method('markPasswordInvalid')
+			->with($token, 'APP-PASSWORD');
+		$userSession->expects($this->once())
+			->method('logout');
+
+		$userSession->setUser($user);
+		$this->invokePrivate($userSession, 'validateSession');
+	}
+
 	public function testUpdateSessionTokenPassword() {
 		$userManager = $this->createMock(Manager::class);
 		$session = $this->createMock(ISession::class);
@@ -1362,4 +1411,12 @@ class SessionTest extends \Test\TestCase {
 
 		$this->assertFalse($userSession->tryBasicAuthLogin($request, $this->throttler));
 	}
+
+	public function testUpdateTokens() {
+		$this->tokenProvider->expects($this->once())
+			->method('updatePasswords')
+			->with('uid', 'pass');
+
+		$this->userSession->updateTokens('uid', 'pass');
+	}
 }