HelloJohn / docs
MFA

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

LevelScopeUse Case
GlobalAll users in the tenantEnforce MFA for all users
Per-OrganizationMembers of a specific orgRequire MFA for enterprise customers
Step-UpSpecific actionsRequire MFA re-verification for sensitive operations
User-SelectedUser's choiceAllow 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: false until enrollment is complete
  • Your app should check mfa_verified and 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"]
  }'
FactorValue
TOTP (authenticator apps)totp
SMS OTPsms
Email OTPemail_otp
WebAuthn / Passkeyswebauthn
Backup Codesbackup_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
  }
}

On this page