From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Modrinth Enhanced Date: Mon, 14 Sep 2026 14:43:17 +0200 Subject: [PATCH] Sign in to Microsoft in the player's own browser The launcher opened Microsoft's sign-in page in a webview of its own, which is the one place a player cannot use the tools they use everywhere else: no password manager, no autofill, no passkeys, and no way to tell by looking that the page is really Microsoft's. The browser gets all of it. What it cannot do is hand the code back: this client id is Minecraft's own, its only registered redirect is a fixed page on login.live.com, and no loopback address is registered for it, so there is nothing for the launcher to listen on. The browser therefore lands on that page with the code in the address, and the player copies the address over - which is what the new modal asks for, and what `login_browser_finish` reads the code out of. Every entry point goes through it, since they all end up at `AccountsCard.login()`. The webview is still one click away in that modal for anyone the browser does not work out for. --- .../src/components/ui/AccountsCard.vue | 17 +- .../src/components/ui/MicrosoftLoginModal.vue | 213 ++++++++++++++++++ .../MinecraftRequiredModal.vue | 29 +-- apps/app-frontend/src/helpers/auth.js | 25 ++ apps/app/src/api/auth.rs | 77 +++++++ packages/app-lib/src/api/mod.rs | 12 +- 6 files changed, 331 insertions(+), 42 deletions(-) create mode 100644 apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue diff --git a/apps/app-frontend/src/components/ui/AccountsCard.vue b/apps/app-frontend/src/components/ui/AccountsCard.vue index 35c21fa..c81603c 100644 --- a/apps/app-frontend/src/components/ui/AccountsCard.vue +++ b/apps/app-frontend/src/components/ui/AccountsCard.vue @@ -105,6 +105,7 @@ + @@ -133,13 +134,12 @@ import type { Ref } from 'vue' import { computed, onUnmounted, ref } from 'vue' import ElyAccountModal from '@/components/ui/ElyAccountModal.vue' +import MicrosoftLoginModal from '@/components/ui/MicrosoftLoginModal.vue' import OfflineAccountModal from '@/components/ui/OfflineAccountModal.vue' import { useAppEvent } from '@/composables/use-app-event' -import { handleSevereError } from '@/composables/use-error.js' import { trackEvent } from '@/helpers/analytics' import { get_default_user, - login as login_flow, remove_user, set_default_user, users, @@ -165,6 +165,7 @@ type MinecraftCredential = { const accounts: Ref = ref([]) const offlineAccountModal = ref>() const elyAccountModal = ref>() +const microsoftLoginModal = ref>() const loginDisabled = ref(false) const defaultUser = ref() const equippedSkin = ref(null) @@ -275,16 +276,8 @@ async function setAccount(account: MinecraftCredential) { emit('change') } -async function login() { - loginDisabled.value = true - const loggedIn = await login_flow().catch(handleSevereError) - - if (loggedIn) { - await setAccount(loggedIn) - } - - trackEvent('AccountLogIn') - loginDisabled.value = false +function login(event?: MouseEvent) { + microsoftLoginModal.value?.show(event) } async function accountAdded() { diff --git a/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue b/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue new file mode 100644 index 0000000..283b89c --- /dev/null +++ b/apps/app-frontend/src/components/ui/MicrosoftLoginModal.vue @@ -0,0 +1,213 @@ + + + diff --git a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue index ec939d7..b1e5358 100644 --- a/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue +++ b/apps/app-frontend/src/components/ui/minecraft-required-modal/MinecraftRequiredModal.vue @@ -25,10 +25,8 @@ {{ formatMessage(messages.getSupport) }} -