MFA Policies
Configure MFA enforcement rules — global requirements, per-organization settings, and step-up authentication.
MFA Policies
MFA policies define when and how multi-factor authentication is required in your application. HelloJohn supports global policies, per-organization overrides, and step-up enforcement.
Policy Levels
| Level | Scope | Use Case |
|---|---|---|
| Global | All users in the tenant | Enforce MFA for all users |
| Per-Organization | Members of a specific org | Require MFA for enterprise customers |
| Step-Up | Specific actions | Require MFA re-verification for sensitive operations |
| User-Selected | User's choice | Allow but don't require MFA |
Global MFA Enforcement
Require all users to enroll in MFA before accessing the application:
curl -X PATCH "https://api.hellojohn.dev/v1/admin/config" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"mfa_required": true}'When mfa_required is true:
- Users without MFA are redirected to enrollment after sign-in
- The access token has
mfa_verified: falseuntil enrollment is complete - Your app should check
mfa_verifiedand redirect to the MFA setup page
// Middleware to enforce MFA enrollment
function requireMFAEnrolled(req: Request, res: Response, next: NextFunction) {
if (!req.token.mfa_verified) {
return res.status(403).json({
error: "mfa_enrollment_required",
redirect: "/mfa/setup",
});
}
next();
}Allowed Factor Types
Restrict which MFA methods users can enroll:
curl -X PATCH "https://api.hellojohn.dev/v1/admin/config" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{
"mfa_allowed_factors": ["totp", "webauthn", "backup_codes"]
}'| Factor | Value |
|---|---|
| TOTP (authenticator apps) | totp |
| SMS OTP | sms |
| Email OTP | email_otp |
| WebAuthn / Passkeys | webauthn |
| Backup Codes | backup_codes |
Setting mfa_allowed_factors prevents users from enrolling disallowed factors. Backup codes are included automatically when any other factor is enabled.
Per-Organization MFA Enforcement
Require MFA only for members of specific organizations — useful for enterprise customers who require it:
curl -X PATCH "https://api.hellojohn.dev/v1/organizations/org_01HABCDEF777666" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{"mfa_required": true}'When a user switches to this organization, the JWT reflects mfa_verified. If they haven't completed MFA, they're prompted to do so.
Step-Up Authentication
Require fresh MFA verification for sensitive operations — without signing the user out:
// Backend middleware for sensitive endpoints
function requireRecentMFA(maxAgeSeconds = 300) {
return (req: Request, res: Response, next: NextFunction) => {
const { mfa_verified, mfa_verified_at } = req.token;
if (!mfa_verified) {
return res.status(403).json({
error: "mfa_step_up_required",
message: "Please verify your identity to continue.",
});
}
const age = Date.now() / 1000 - (mfa_verified_at ?? 0);
if (age > maxAgeSeconds) {
return res.status(403).json({
error: "mfa_step_up_required",
message: "MFA verification has expired. Please verify again.",
});
}
next();
};
}
app.post("/account/delete", requireRecentMFA(300), handleDeleteAccount);
app.post("/payment/send", requireRecentMFA(60), handlePayment);Triggering Step-Up in the Client
import { useHelloJohn } from "@hellojohn/react";
async function performSensitiveAction() {
try {
await fetch("/account/delete", { method: "POST" });
} catch (error) {
if (error.code === "mfa_step_up_required") {
// Prompt user to complete MFA
await hj.mfa.stepUp();
// Retry after MFA
await fetch("/account/delete", { method: "POST" });
}
}
}Grace Period
Allow users a grace period to enroll in MFA after the policy is enabled. Useful when rolling out mandatory MFA to existing users:
curl -X PATCH "https://api.hellojohn.dev/v1/admin/config" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321" \
-H "Content-Type: application/json" \
-d '{
"mfa_required": true,
"mfa_enrollment_grace_period": "14d"
}'During the grace period, users can sign in without MFA. After it expires, unenrolled users must enroll before accessing the app.
Viewing MFA Enrollment Rate
Monitor MFA adoption via tenant stats:
curl "https://api.hellojohn.dev/v1/admin/stats" \
-H "Authorization: Bearer sk_live_abc123" \
-H "X-Tenant-ID: tnt_01HABCDEF654321"{
"mfa": {
"enrolled_users": 623,
"enrollment_rate": 0.34
}
}