diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index e63d501651..d29b96af1d 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1112,6 +1112,7 @@ desktop/views/components/settings.2fa.vue:
   register-security-key: "キーの登録を完了"
   something-went-wrong: "わー! キーを登録する際に問題が発生しました:"
   key-unregistered: "キーが削除されました"
+  use-password-less-login: "パスワードなしのログインを使用"
 
 common/views/components/media-image.vue:
   sensitive: "閲覧注意"
diff --git a/migration/1562422242907-PasswordLessLogin.ts b/migration/1562422242907-PasswordLessLogin.ts
new file mode 100644
index 0000000000..e789a34334
--- /dev/null
+++ b/migration/1562422242907-PasswordLessLogin.ts
@@ -0,0 +1,13 @@
+import {MigrationInterface, QueryRunner} from "typeorm";
+
+export class PasswordLessLogin1562422242907 implements MigrationInterface {
+
+	public async up(queryRunner: QueryRunner): Promise<any> {
+		await queryRunner.query(`ALTER TABLE "user_profile" ADD COLUMN "usePasswordLessLogin" boolean DEFAULT false NOT NULL`);
+	}
+
+	public async down(queryRunner: QueryRunner): Promise<any> {
+		await queryRunner.query(`ALTER TABLE "user_profile" DROP COLUMN "usePasswordLessLogin"`);
+	}
+
+}
diff --git a/src/client/app/common/views/components/settings/2fa.vue b/src/client/app/common/views/components/settings/2fa.vue
index eb645898e2..813a91b5c0 100644
--- a/src/client/app/common/views/components/settings/2fa.vue
+++ b/src/client/app/common/views/components/settings/2fa.vue
@@ -28,6 +28,10 @@
 				</div>
 			</div>
 
+			<ui-switch v-model="usePasswordLessLogin" @change="updatePasswordLessLogin" v-if="$store.state.i.securityKeysList.length > 0">
+				{{ $t('use-password-less-login') }}
+			</ui-switch>
+
 			<ui-info warn v-if="registration && registration.error">{{ $t('something-went-wrong') }} {{ registration.error }}</ui-info>
 			<ui-button v-if="!registration || registration.error" @click="addSecurityKey">{{ $t('register') }}</ui-button>
 
@@ -80,6 +84,7 @@ export default Vue.extend({
 		return {
 			data: null,
 			supportsCredentials: !!navigator.credentials,
+			usePasswordLessLogin: this.$store.state.i.usePasswordLessLogin,
 			registration: null,
 			keyName: '',
 			token: null
@@ -112,6 +117,9 @@ export default Vue.extend({
 				if (canceled) return;
 				this.$root.api('i/2fa/unregister', {
 					password: password
+				}).then(() => {
+					this.usePasswordLessLogin = false;
+					this.updatePasswordLessLogin();
 				}).then(() => {
 					this.$notify(this.$t('unregistered'));
 					this.$store.state.i.twoFactorEnabled = false;
@@ -157,6 +165,9 @@ export default Vue.extend({
 				return this.$root.api('i/2fa/remove-key', {
 					password,
 					credentialId: key.id
+				}).then(() => {
+					this.usePasswordLessLogin = false;
+					this.updatePasswordLessLogin();
 				}).then(() => {
 					this.$notify(this.$t('key-unregistered'));
 				});
@@ -213,6 +224,11 @@ export default Vue.extend({
 					this.registration.stage = -1;
 				});
 			});
+		},
+		updatePasswordLessLogin() {
+			this.$root.api('i/2fa/password-less', {
+				value: !!this.usePasswordLessLogin
+			});
 		}
 	}
 });
diff --git a/src/client/app/common/views/components/signin.vue b/src/client/app/common/views/components/signin.vue
index 8498a1dc3e..f76f989d6d 100644
--- a/src/client/app/common/views/components/signin.vue
+++ b/src/client/app/common/views/components/signin.vue
@@ -7,7 +7,7 @@
 			<template #prefix>@</template>
 			<template #suffix>@{{ host }}</template>
 		</ui-input>
-		<ui-input v-model="password" type="password" :with-password-toggle="true" required>
+		<ui-input v-model="password" type="password" :with-password-toggle="true" v-if="!user || user && !user.usePasswordLessLogin" required>
 			<span>{{ $t('password') }}</span>
 			<template #prefix><fa icon="lock"/></template>
 		</ui-input>
@@ -28,6 +28,10 @@
 		</div>
 		<div class="twofa-group totp-group">
 			<p style="margin-bottom:0;">{{ $t('enter-2fa-code') }}</p>
+			<ui-input v-model="password" type="password" :with-password-toggle="true" v-if="user && user.usePasswordLessLogin" required>
+				<span>{{ $t('password') }}</span>
+				<template #prefix><fa icon="lock"/></template>
+			</ui-input>
 			<ui-input v-model="token" type="text" pattern="^[0-9]{6}$" autocomplete="off" spellcheck="false" required>
 				<span>{{ $t('@.2fa') }}</span>
 				<template #prefix><fa icon="gavel"/></template>
diff --git a/src/models/entities/user-profile.ts b/src/models/entities/user-profile.ts
index 6f960f1b7b..4a588ebfbf 100644
--- a/src/models/entities/user-profile.ts
+++ b/src/models/entities/user-profile.ts
@@ -81,6 +81,11 @@ export class UserProfile {
 	})
 	public securityKeysAvailable: boolean;
 
+	@Column('boolean', {
+		default: false,
+	})
+	public usePasswordLessLogin: boolean;
+
 	@Column('varchar', {
 		length: 128, nullable: true,
 		comment: 'The password hash of the User. It will be null if the origin of the user is local.'
diff --git a/src/models/repositories/user.ts b/src/models/repositories/user.ts
index cc89b674c5..06da74197f 100644
--- a/src/models/repositories/user.ts
+++ b/src/models/repositories/user.ts
@@ -156,6 +156,7 @@ export class UserRepository extends Repository<User> {
 					detail: true
 				}),
 				twoFactorEnabled: profile!.twoFactorEnabled,
+				usePasswordLessLogin: profile!.usePasswordLessLogin,
 				securityKeys: profile!.twoFactorEnabled
 					? UserSecurityKeys.count({
 						userId: user.id
@@ -208,7 +209,6 @@ export class UserRepository extends Repository<User> {
 						select: ['id', 'name', 'lastUsed']
 					})
 					: []
-
 			} : {}),
 
 			...(relation ? {
diff --git a/src/server/api/endpoints/i/2fa/password-less.ts b/src/server/api/endpoints/i/2fa/password-less.ts
new file mode 100644
index 0000000000..19e75ca1c5
--- /dev/null
+++ b/src/server/api/endpoints/i/2fa/password-less.ts
@@ -0,0 +1,21 @@
+import $ from 'cafy';
+import define from '../../../define';
+import { UserProfiles } from '../../../../../models';
+
+export const meta = {
+	requireCredential: true,
+
+	secure: true,
+
+	params: {
+		value: {
+			validator: $.boolean
+		}
+	}
+};
+
+export default define(meta, async (ps, user) => {
+	await UserProfiles.update(user.id, {
+		usePasswordLessLogin: ps.value
+	});
+});
diff --git a/src/server/api/private/signin.ts b/src/server/api/private/signin.ts
index bc9346d088..67afed760b 100644
--- a/src/server/api/private/signin.ts
+++ b/src/server/api/private/signin.ts
@@ -72,19 +72,25 @@ export default async (ctx: Koa.BaseContext) => {
 		}
 	}
 
-	if (!same) {
-		await fail(403, {
-			error: 'incorrect password'
-		});
-		return;
-	}
-
 	if (!profile.twoFactorEnabled) {
-		signin(ctx, user);
+		if (same) {
+			signin(ctx, user);
+		} else {
+			await fail(403, {
+				error: 'incorrect password'
+			});
+		}
 		return;
 	}
 
 	if (token) {
+		if (!same) {
+			await fail(403, {
+				error: 'incorrect password'
+			});
+			return;
+		}
+
 		const verified = (speakeasy as any).totp.verify({
 			secret: profile.twoFactorSecret,
 			encoding: 'base32',
@@ -101,6 +107,13 @@ export default async (ctx: Koa.BaseContext) => {
 			return;
 		}
 	} else if (body.credentialId) {
+		if (!same && !profile.usePasswordLessLogin) {
+			await fail(403, {
+				error: 'incorrect password'
+			});
+			return;
+		}
+
 		const clientDataJSON = Buffer.from(body.clientDataJSON, 'hex');
 		const clientData = JSON.parse(clientDataJSON.toString('utf-8'));
 		const challenge = await AttestationChallenges.findOne({
@@ -163,6 +176,13 @@ export default async (ctx: Koa.BaseContext) => {
 			return;
 		}
 	} else {
+		if (!same && !profile.usePasswordLessLogin) {
+			await fail(403, {
+				error: 'incorrect password'
+			});
+			return;
+		}
+
 		const keys = await UserSecurityKeys.find({
 			userId: user.id
 		});