diff --git a/locales/index.d.ts b/locales/index.d.ts
index f0dead1245..c21d89e8f8 100644
--- a/locales/index.d.ts
+++ b/locales/index.d.ts
@@ -5158,6 +5158,10 @@ export interface Locale extends ILocale {
      * パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。
      */
     "passkeyVerificationSucceededButPasswordlessLoginDisabled": string;
+    /**
+     * お使いのブラウザはパスキーをサポートしていません。
+     */
+    "yourBrowserDoesNotSupportPasskey": string;
     /**
      * フォロワーへのメッセージ
      */
diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml
index 0076c467ec..3ee6ab1cee 100644
--- a/locales/ja-JP.yml
+++ b/locales/ja-JP.yml
@@ -1285,6 +1285,7 @@ signinWithPasskey: "パスキーでログイン"
 unknownWebAuthnKey: "登録されていないパスキーです。"
 passkeyVerificationFailed: "パスキーの検証に失敗しました。"
 passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスキーの検証に成功しましたが、パスワードレスログインが無効になっています。"
+yourBrowserDoesNotSupportPasskey: "お使いのブラウザはパスキーをサポートしていません。"
 messageToFollower: "フォロワーへのメッセージ"
 target: "対象"
 
diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts
index 0d24ffa56a..d608ad527e 100644
--- a/packages/backend/src/server/api/SigninApiService.ts
+++ b/packages/backend/src/server/api/SigninApiService.ts
@@ -136,6 +136,17 @@ export class SigninApiService {
 		if (password == null) {
 			reply.code(200);
 			if (profile.twoFactorEnabled) {
+				if (profile.usePasswordLessLogin && securityKeysAvailable) {
+					const authRequest = await this.webAuthnService.initiateAuthentication(user.id);
+
+					return {
+						finished: false,
+						next: 'passkey',
+						force: true,
+						authRequest,
+					} satisfies Misskey.entities.SigninFlowResponse;
+				}
+
 				return {
 					finished: false,
 					next: 'password',
diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue
index 34c22abc31..da54a0967f 100644
--- a/packages/frontend/src/components/MkSignin.input.vue
+++ b/packages/frontend/src/components/MkSignin.input.vue
@@ -40,14 +40,16 @@ SPDX-License-Identifier: AGPL-3.0-only
 		</form>
 
 		<!-- パスワードレスログイン -->
-		<div :class="$style.orHr">
-			<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
-		</div>
-		<div>
-			<MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)">
-				<i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }}
-			</MkButton>
-		</div>
+		<template v-if="webAuthnSupported()">
+			<div :class="$style.orHr">
+				<p :class="$style.orMsg">{{ i18n.ts.or }}</p>
+			</div>
+			<div>
+				<MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)">
+					<i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }}
+				</MkButton>
+			</div>
+		</template>
 	</div>
 </div>
 </template>
@@ -55,6 +57,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 <script setup lang="ts">
 import { ref } from 'vue';
 import { toUnicode } from 'punycode/';
+import { supported as webAuthnSupported } from '@github/webauthn-json/browser-ponyfill';
 
 import { query, extractDomain } from '@@/js/url.js';
 import { host as configHost } from '@@/js/config.js';
diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue
index a773cefdab..2a6d19b6c5 100644
--- a/packages/frontend/src/components/MkSignin.vue
+++ b/packages/frontend/src/components/MkSignin.vue
@@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only
 			key="passkey"
 
 			:credentialRequest="credentialRequest!"
-			:isPerformingPasswordlessLogin="doingPasskeyFromInputPage"
+			:isPerformingPasswordlessLogin="doingPasskeyFromInputPage || needForcedPasskey"
 
 			@done="onPasskeyDone"
 			@useTotp="onUseTotp"
@@ -101,6 +101,7 @@ const waiting = ref(false);
 
 const passwordPageEl = useTemplateRef('passwordPageEl');
 const needCaptcha = ref(false);
+const needForcedPasskey = ref(false);
 
 const userInfo = ref<null | Misskey.entities.UserDetailed>(null);
 const password = ref('');
@@ -247,7 +248,19 @@ async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promi
 					break;
 				}
 				case 'passkey': {
-					if (webAuthnSupported()) {
+					if (res.force === true) {
+						if (webAuthnSupported()) {
+							needForcedPasskey.value = true;
+							credentialRequest.value = parseRequestOptionsFromJSON({
+								publicKey: res.authRequest,
+							});
+							page.value = 'passkey';
+						} else {
+							throw {
+								id: '8b12bdf5-d5ed-4429-b5da-e3370cfcb869',
+							};
+						}
+					} else if (webAuthnSupported()) {
 						credentialRequest.value = parseRequestOptionsFromJSON({
 							publicKey: res.authRequest,
 						});
@@ -264,6 +277,9 @@ async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promi
 				page.value = 'input';
 				password.value = '';
 			}
+			if (!('force' in res)) {
+				needForcedPasskey.value = false;
+			}
 			passwordPageEl.value?.resetCaptcha();
 			nextTick(() => {
 				waiting.value = false;
@@ -286,6 +302,7 @@ function onSigninApiError(err?: any): void {
 	const id = err?.id ?? null;
 
 	switch (id) {
+		// signin-flow api
 		case '6cc579cc-885d-43d8-95c2-b8c7fc963280': {
 			os.alert({
 				type: 'error',
@@ -338,6 +355,8 @@ function onSigninApiError(err?: any): void {
 			});
 			break;
 		}
+
+		// signin-with-passkey api
 		case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': {
 			os.alert({
 				type: 'error',
@@ -354,6 +373,18 @@ function onSigninApiError(err?: any): void {
 			});
 			break;
 		}
+
+		// client-produced error
+		case '8b12bdf5-d5ed-4429-b5da-e3370cfcb869': {
+			os.alert({
+				type: 'error',
+				title: i18n.ts.loginFailed,
+				text: i18n.ts.yourBrowserDoesNotSupportPasskey,
+			});
+			break;
+		}
+
+		// default
 		default: {
 			console.error(err);
 			os.alert({
@@ -369,6 +400,7 @@ function onSigninApiError(err?: any): void {
 		page.value = 'input';
 		password.value = '';
 	}
+	needForcedPasskey.value = false;
 	passwordPageEl.value?.resetCaptcha();
 	nextTick(() => {
 		waiting.value = false;
diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md
index 72c236373d..37342db5c1 100644
--- a/packages/misskey-js/etc/misskey-js.api.md
+++ b/packages/misskey-js/etc/misskey-js.api.md
@@ -3078,6 +3078,7 @@ type SigninFlowResponse = {
 } | {
     finished: false;
     next: 'passkey';
+    force?: boolean;
     authRequest: PublicKeyCredentialRequestOptionsJSON;
 };
 
diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts
index dd88791ed0..39479df33b 100644
--- a/packages/misskey-js/src/entities.ts
+++ b/packages/misskey-js/src/entities.ts
@@ -294,6 +294,7 @@ export type SigninFlowResponse = {
 } | {
 	finished: false;
 	next: 'passkey';
+	force?: boolean;
 	authRequest: PublicKeyCredentialRequestOptionsJSON;
 };