Last active
December 30, 2025 13:07
-
-
Save SSPLGautam/f2f9fafea5240e199ddf6f2ff938ae34 to your computer and use it in GitHub Desktop.
Force Users to Enable Two-Factor Authentication in Umbraco 14+ (14, 15, 16, 17)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import type { UmbEntryPointOnInit } from '@umbraco-cms/backoffice/extension-api'; | |
| import { | |
| UmbCurrentUserRepository, | |
| type UmbCurrentUserModel, | |
| UMB_CURRENT_USER_MFA_MODAL | |
| } from '@umbraco-cms/backoffice/current-user'; | |
| import { UMB_MODAL_MANAGER_CONTEXT } from '@umbraco-cms/backoffice/modal'; | |
| //import { UMB_NOTIFICATION_CONTEXT } from '@umbraco-cms/backoffice/notification'; | |
| import type { UmbElement } from '@umbraco-cms/backoffice/element-api'; | |
| import type { UmbControllerHost } from '@umbraco-cms/backoffice/controller-api'; | |
| /** | |
| * Entry point: Enforces 2FA if not enabled on the current backoffice user. | |
| */ | |
| export const onInit: UmbEntryPointOnInit = async (host) => { | |
| try { | |
| const isEnabled = await isTwoFactorEnabled(host); | |
| // If already enabled, do nothing | |
| if (isEnabled) return; | |
| const currentUserRepo = new UmbCurrentUserRepository(host); | |
| const { data: currentUser } = await currentUserRepo.requestCurrentUser(); | |
| const { data: providers } = await currentUserRepo.requestMfaLoginProviders(); | |
| if (!providers || providers.length === 0) { | |
| console.error('[2FA Enforcement] No MFA providers found.'); | |
| return; | |
| } | |
| if (!currentUser?.unique) { | |
| console.error('[2FA Enforcement] Failed to retrieve user or unique identifier.'); | |
| return; | |
| } | |
| // Delay ensures backoffice UI is fully initialized before modal opens | |
| setTimeout(() => { | |
| open2faModal(host, currentUser).catch((err) => { | |
| console.error('[2FA Enforcement] Failed to open MFA modal.', err); | |
| }); | |
| }, 500); | |
| } catch (error) { | |
| console.error('[2FA Enforcement] Initialization failed:', error); | |
| } | |
| }; | |
| /** | |
| * Checks if current user has 2FA enabled. | |
| */ | |
| export const isTwoFactorEnabled = async (host: UmbControllerHost): Promise<boolean> => { | |
| try { | |
| const repo = new UmbCurrentUserRepository(host); | |
| const { data: providers } = await repo.requestMfaLoginProviders(); | |
| return Boolean(providers?.[0]?.isEnabledOnUser); | |
| } catch (error) { | |
| console.error('[2FA Enforcement] Failed to check 2FA status:', error); | |
| return false; | |
| } | |
| }; | |
| /** | |
| * Opens the 2FA modal and enforces enabling it. | |
| * If user closes the modal without enabling, it reopens. | |
| */ | |
| export const open2faModal = async ( | |
| host: UmbElement, | |
| currentUser: UmbCurrentUserModel | |
| ): Promise<void> => { | |
| try { | |
| const modalManagerContext = await host.getContext(UMB_MODAL_MANAGER_CONTEXT); | |
| await modalManagerContext | |
| .open(host, UMB_CURRENT_USER_MFA_MODAL, { | |
| data: { | |
| unique: currentUser.unique, | |
| }, | |
| }) | |
| .onSubmit() | |
| .catch((error) => { | |
| console.warn('[2FA Enforcement] MFA modal cancelled or errored.', error); | |
| }); | |
| const enabled = await isTwoFactorEnabled(host); | |
| // If not enabled, Reopen modal until user enables 2FA | |
| if (!enabled) { | |
| // TODO: show notification of mandatory 2fa config here, the below line gets hidden under the 2FA modal | |
| //(await host.getContext(UMB_NOTIFICATION_CONTEXT))?.peek('danger', { | |
| // data: { | |
| // headline: "Two-Factor Auth", | |
| // message: "Please configure two-factor authentication.", | |
| // } | |
| //}); | |
| return open2faModal(host, currentUser); | |
| } | |
| else { | |
| // TODO: show success notification here, the below line gets hidden under the 2FA modal | |
| //(await host.getContext(UMB_NOTIFICATION_CONTEXT))?.peek('positive', { | |
| // color: 'positive', | |
| // data: { | |
| // headline: "Two-Factor Auth", | |
| // message: "You have successfully configured two-factor authentication.", | |
| // } | |
| //}); | |
| } | |
| } catch (err) { | |
| // Swallow modal-cancel errors silently (matching original behavior) | |
| console.warn('[2FA Enforcement] MFA modal cancelled or errored.', err); | |
| } | |
| }; |
Author
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
This is the .ts file. You'll need to configure the cite setup and build the client project that results the .js file under App_Plugins.
For vite setup, follow this document: https://docs.umbraco.com/umbraco-cms/customizing/development-flow/vite-package-setup.
Add these lines of code in the "extensions" of
umbraco-package.json: