diff --git a/src/data/standards.json b/src/data/standards.json
index 2eee70c7420b..6fd4e3d3b64e 100644
--- a/src/data/standards.json
+++ b/src/data/standards.json
@@ -1371,21 +1371,38 @@
"cat": "Entra (AAD) Standards",
"tag": ["SMB1001 (2.5)"],
"appliesToTest": ["SMB1001_2_5", "ZTNA21889"],
- "helpText": "Sets the state of the registration campaign for the tenant",
- "docsDescription": "Sets the state of the registration campaign for the tenant. If enabled nudges users to set up the Microsoft Authenticator during sign-in.",
+ "helpText": "Sets the state of the registration campaign for the tenant, including the targeted authentication method, snooze settings and include/exclude groups. Leave include/exclude blank to keep the groups currently configured in the tenant, or use 'AllUsers' to target all users.",
+ "docsDescription": "Sets the state of the registration campaign for the tenant. If enabled nudges users to set up the targeted authentication method (Microsoft Authenticator or a Passkey) during sign-in. Supports limiting the number of snoozes, and including or excluding specific groups (by display name).",
"executiveText": "Prompts employees to set up multi-factor authentication during login, gradually improving the organization's security posture by encouraging adoption of stronger authentication methods. This helps achieve better security compliance without forcing immediate mandatory changes.",
"addedComponent": [
{
"type": "autoComplete",
"multiple": false,
"creatable": false,
- "label": "Select value",
+ "label": "Registration campaign state",
"name": "standards.NudgeMFA.state",
"options": [
{ "label": "Enabled", "value": "enabled" },
{ "label": "Disabled", "value": "disabled" }
]
},
+ {
+ "type": "autoComplete",
+ "multiple": false,
+ "creatable": false,
+ "required": false,
+ "label": "Authentication method to nudge users to register (default is Microsoft Authenticator)",
+ "name": "standards.NudgeMFA.targetedAuthenticationMethod",
+ "options": [
+ { "label": "Microsoft Authenticator", "value": "microsoftAuthenticator" },
+ { "label": "Passkey (FIDO2)", "value": "fido2" }
+ ],
+ "condition": {
+ "field": "standards.NudgeMFA.state",
+ "compareType": "valueEq",
+ "compareValue": "enabled"
+ }
+ },
{
"type": "number",
"name": "standards.NudgeMFA.snoozeDurationInDays",
@@ -1395,6 +1412,39 @@
"min": { "value": 0, "message": "Minimum value is 0" },
"max": { "value": 14, "message": "Maximum value is 14" }
}
+ },
+ {
+ "type": "switch",
+ "name": "standards.NudgeMFA.enforceRegistrationAfterAllowedSnoozes",
+ "label": "Limited number of snoozes (require registration after 3 snoozes)",
+ "defaultValue": true,
+ "condition": {
+ "field": "standards.NudgeMFA.state",
+ "compareType": "valueEq",
+ "compareValue": "enabled"
+ }
+ },
+ {
+ "type": "textField",
+ "name": "standards.NudgeMFA.includeTargets",
+ "label": "Include groups (comma separated group names, 'AllUsers' for everyone, blank = keep current targets)",
+ "required": false,
+ "condition": {
+ "field": "standards.NudgeMFA.state",
+ "compareType": "valueEq",
+ "compareValue": "enabled"
+ }
+ },
+ {
+ "type": "textField",
+ "name": "standards.NudgeMFA.excludeTargets",
+ "label": "Exclude groups (comma separated group names, blank = keep current exclusions)",
+ "required": false,
+ "condition": {
+ "field": "standards.NudgeMFA.state",
+ "compareType": "valueEq",
+ "compareValue": "enabled"
+ }
}
],
"label": "Sets the state for the request to setup Authenticator",
diff --git a/src/pages/tenant/administration/authentication-methods/index.js b/src/pages/tenant/administration/authentication-methods/index.js
index f865d6a0172b..2961fb8e4467 100644
--- a/src/pages/tenant/administration/authentication-methods/index.js
+++ b/src/pages/tenant/administration/authentication-methods/index.js
@@ -1,4 +1,6 @@
import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { TabbedLayout } from "../../../../layouts/TabbedLayout";
+import tabOptions from "./tabOptions.json";
import { CippTablePage } from "../../../../components/CippComponents/CippTablePage.jsx";
import CippFormComponent from "../../../../components/CippComponents/CippFormComponent.jsx";
import { Box } from "@mui/material";
@@ -7,7 +9,7 @@ import { UserGroupIcon } from "@heroicons/react/24/outline";
import { useSettings } from "../../../../hooks/use-settings.js";
const Page = () => {
- const pageTitle = "Auth Methods";
+ const pageTitle = "Policies";
const tenant = useSettings().currentTenant;
const apiUrl = "/api/ListGraphRequest";
@@ -357,6 +359,10 @@ const Page = () => {
};
// Adding the layout for the dashboard
-Page.getLayout = (page) => {page};
+Page.getLayout = (page) => (
+
+ {page}
+
+);
export default Page;
diff --git a/src/pages/tenant/administration/authentication-methods/registration-campaign.js b/src/pages/tenant/administration/authentication-methods/registration-campaign.js
new file mode 100644
index 000000000000..2658cba5dbb3
--- /dev/null
+++ b/src/pages/tenant/administration/authentication-methods/registration-campaign.js
@@ -0,0 +1,252 @@
+import { useEffect } from "react";
+import { useForm } from "react-hook-form";
+import { Alert, Typography } from "@mui/material";
+import { Grid } from "@mui/system";
+import { Layout as DashboardLayout } from "../../../../layouts/index.js";
+import { TabbedLayout } from "../../../../layouts/TabbedLayout";
+import tabOptions from "./tabOptions.json";
+import CippFormPage from "../../../../components/CippFormPages/CippFormPage";
+import CippFormComponent from "../../../../components/CippComponents/CippFormComponent";
+import { ApiGetCall } from "../../../../api/ApiCall";
+import { useSettings } from "../../../../hooks/use-settings.js";
+
+const stateOptions = [
+ { label: "Microsoft managed", value: "default" },
+ { label: "Enabled", value: "enabled" },
+ { label: "Disabled", value: "disabled" },
+];
+
+const methodOptions = [
+ { label: "Microsoft Authenticator", value: "microsoftAuthenticator" },
+ { label: "Passkey (FIDO2)", value: "fido2" },
+];
+
+// Map campaign targets of one type to autocomplete options (all_users is handled by its own switch)
+const targetsToOptions = (targets, targetType) =>
+ (Array.isArray(targets) ? targets : [])
+ .filter((target) => target?.targetType === targetType && target?.id !== "all_users")
+ .map((target) => ({ label: target.id, value: target.id }));
+
+const toIdArray = (value) =>
+ Array.isArray(value) ? value.map((item) => item.value).filter(Boolean) : [];
+
+const Page = () => {
+ const tenant = useSettings().currentTenant;
+ const queryKey = `RegistrationCampaign-${tenant}`;
+
+ const formControl = useForm({
+ mode: "onChange",
+ });
+
+ const campaignRequest = ApiGetCall({
+ url: "/api/ListGraphRequest",
+ data: {
+ Endpoint: "authenticationMethodsPolicy",
+ tenantFilter: tenant,
+ },
+ queryKey: queryKey,
+ });
+
+ const campaign =
+ campaignRequest.data?.Results?.[0]?.registrationEnforcement
+ ?.authenticationMethodsRegistrationCampaign;
+
+ useEffect(() => {
+ if (campaignRequest.isSuccess && campaign) {
+ formControl.reset({
+ state: stateOptions.find((option) => option.value === campaign.state) ?? stateOptions[0],
+ targetedAuthenticationMethod:
+ methodOptions.find(
+ (option) => option.value === campaign.includeTargets?.[0]?.targetedAuthenticationMethod,
+ ) ?? methodOptions[0],
+ snoozeDurationInDays: campaign.snoozeDurationInDays,
+ enforceRegistrationAfterAllowedSnoozes: !!campaign.enforceRegistrationAfterAllowedSnoozes,
+ includeAllUsers: (Array.isArray(campaign.includeTargets)
+ ? campaign.includeTargets
+ : []
+ ).some((target) => target?.id === "all_users"),
+ includeGroups: targetsToOptions(campaign.includeTargets, "group"),
+ includeUsers: targetsToOptions(campaign.includeTargets, "user"),
+ excludeGroups: targetsToOptions(campaign.excludeTargets, "group"),
+ excludeUsers: targetsToOptions(campaign.excludeTargets, "user"),
+ });
+ }
+ }, [campaignRequest.isSuccess, campaign]);
+
+ const groupFieldApi = {
+ url: "/api/ListGraphRequest",
+ dataKey: "Results",
+ queryKey: `RegistrationCampaignGroups-${tenant}`,
+ labelField: (group) => (group.id ? `${group.displayName} (${group.id})` : group.displayName),
+ valueField: "id",
+ data: {
+ Endpoint: "groups",
+ manualPagination: true,
+ $select: "id,displayName",
+ $orderby: "displayName",
+ $top: 999,
+ $count: true,
+ },
+ };
+
+ const userFieldApi = {
+ url: "/api/ListGraphRequest",
+ dataKey: "Results",
+ queryKey: `RegistrationCampaignUsers-${tenant}`,
+ labelField: (user) => `${user.displayName} (${user.userPrincipalName})`,
+ valueField: "id",
+ data: {
+ Endpoint: "users",
+ manualPagination: true,
+ $select: "id,displayName,userPrincipalName",
+ $orderby: "displayName",
+ $top: 999,
+ $count: true,
+ },
+ };
+
+ return (
+ ({
+ tenantFilter: tenant,
+ state: values?.state?.value ?? values?.state,
+ targetedAuthenticationMethod:
+ values?.targetedAuthenticationMethod?.value ?? values?.targetedAuthenticationMethod,
+ snoozeDurationInDays:
+ values?.snoozeDurationInDays === "" || values?.snoozeDurationInDays === undefined
+ ? undefined
+ : Number(values?.snoozeDurationInDays),
+ enforceRegistrationAfterAllowedSnoozes: !!values?.enforceRegistrationAfterAllowedSnoozes,
+ includeAllUsers: !!values?.includeAllUsers,
+ includeGroups: toIdArray(values?.includeGroups),
+ includeUsers: toIdArray(values?.includeUsers),
+ excludeGroups: toIdArray(values?.excludeGroups),
+ excludeUsers: toIdArray(values?.excludeUsers),
+ })}
+ >
+
+
+
+ Nudge users to set up Microsoft Authenticator or a passkey during sign-in. Users are
+ prompted after completing MFA and can snooze the prompt for the configured number of
+ days.
+
+
+ {campaignRequest.isError && (
+
+
+ Failed to load the current registration campaign settings for this tenant.
+
+
+ )}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ );
+};
+
+Page.getLayout = (page) => (
+
+ {page}
+
+);
+
+export default Page;
diff --git a/src/pages/tenant/administration/authentication-methods/tabOptions.json b/src/pages/tenant/administration/authentication-methods/tabOptions.json
new file mode 100644
index 000000000000..7133119254a2
--- /dev/null
+++ b/src/pages/tenant/administration/authentication-methods/tabOptions.json
@@ -0,0 +1,12 @@
+[
+ {
+ "label": "Policies",
+ "path": "/tenant/administration/authentication-methods",
+ "icon": "Key"
+ },
+ {
+ "label": "Registration Campaign",
+ "path": "/tenant/administration/authentication-methods/registration-campaign",
+ "icon": "Notifications"
+ }
+]