Skip to content

Instantly share code, notes, and snippets.

@SSPLGautam
Last active December 30, 2025 13:07
Show Gist options
  • Select an option

  • Save SSPLGautam/f2f9fafea5240e199ddf6f2ff938ae34 to your computer and use it in GitHub Desktop.

Select an option

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)
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);
}
};
@SSPLGautam
Copy link
Author

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:

  {
    "type": "backofficeEntryPoint",
    "alias": "Your.App.EnforceTwoFactor",
    "name": "Enforce Two Factor",
    "js": "/App_Plugins/client/enforceTwoFactorAuthentication/enforceTwoFactorAuthentication.js"
  },

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment