Olympus Docs
CookbookMFA & step-up

Require MFA for admin accounts

Different policies for admins vs users

A common compliance ask: "All admin accounts must have MFA." Implementation in Olympus.

Policy in kratos.yml

Kratos doesn't natively support "MFA required when role = admin", but we can configure it via hooks and session AAL requirements.

# kratos.yml
session:
  whoami:
    required_aal: highest_available  # session uses highest method enrolled

highest_available means: if user has MFA enrolled, login must reach AAL2. Without MFA enrolled, AAL1 is fine.

For admins, we want stricter: must reach AAL2 always, must have MFA enrolled.

Pre-login hook

selfservice:
  flows:
    login:
      before:
        hooks:
          - hook: web_hook
            config:
              url: http://your-backend/check-admin-mfa
              response:
                ignore: false
                parse: true

Backend:

export async function POST(req: Request) {
  const { identity } = await req.json();
  if (identity.traits.role !== "admin") {
    return Response.json({ ok: true });
  }
  
  // Admin must have MFA enrolled
  const credentials = await kratos.adminListCredentials(identity.id);
  const hasMfa = credentials.some(c => 
    c.type === "totp" || c.type === "webauthn" || c.type === "lookup_secret"
  );
  
  if (!hasMfa) {
    return Response.json({
      reject: true,
      redirect_to: "/settings?required=mfa",
      error: "admin_must_enroll_mfa",
    });
  }
  
  return Response.json({ ok: true });
}

Admin without MFA → can't complete login. Redirected to settings to enroll.

Post-login: enforce AAL2

session:
  required_aal: aal2  # for admin paths

But this applies globally. Better: per-route enforcement in Athena's middleware.

// athena/middleware.ts
const session = await kratos.toSession({ cookie });
if (session.identity.traits.role === "admin" && session.authenticator_assurance_level !== "aal2") {
  return NextResponse.redirect("/self-service/login?aal=aal2");
}

Admin path requires AAL2. If user logged in with password only, they're redirected to step-up MFA.

Enforce in your app too

If your app has admin features, gate them similarly:

function requireAdmin(req) {
  if (req.user?.role !== "admin") throw new Forbidden();
  if (req.user?.aal !== "aal2") throw new StepUpRequired();
}

app.get("/admin/*", requireAdmin, ...);

Send aal claim in session/JWT so apps can check.

Grandfathering existing admins

If you're rolling out MFA-required for existing admins:

  1. Today: send notice to all admins. "MFA required by [date]."
  2. Day -7: warning banner in app.
  3. Day 0: hook enforces. Admins without MFA can't log in.
  4. They go through enrollment.

See Feature flag MFA rollout for more.

Allowed MFA methods for admins

Restrict to phishing-resistant:

const acceptable = ["webauthn", "passkey"];  // not TOTP, not SMS
const ok = credentials.some(c => acceptable.includes(c.type));

Admins are high-value targets. TOTP can be phished (the user types it into a phishing page). WebAuthn (origin-bound) cannot.

For maximum security: require hardware key (YubiKey). For most: WebAuthn (passkey).

Audit

Log every MFA enrollment and removal:

INSERT INTO security_audit (event, actor_id, target_id, metadata)
VALUES ('mfa_enrolled', $identity_id, $identity_id, '{"method": "webauthn"}');

If an admin disables their MFA, alarm bells:

await alert("Admin disabled MFA: {{user}}");

Investigate. Phish? Compromise? Stupid?

Block self-disable

Admins should not be able to disable their own MFA without a second admin's approval (4-eyes principle).

Implementation:

  • Settings flow hook on MFA-disable for admin role: reject.
  • "To disable MFA, contact another admin."
// pre-settings hook
if (operation === "mfa_disable" && identity.traits.role === "admin") {
  return Response.json({ reject: true, error: "two_admin_required" });
}

Admin requests disable → another admin approves via Athena → 2-of-N approval recorded → then disabled.

For small teams, this is overkill. For regulated industries: required.

"Cannot enroll MFA" edge cases

  • Admin lost MFA device, can't sign in.
  • Backup codes were never saved.

Recovery: another admin (or break-glass account) resets. See Locked account unlock.

Communicating to users

Subject: 2-factor authentication required for admin accounts

As of [date], all admin accounts require 2-factor authentication. 
Enroll now:

  1. Sign in at https://...
  2. Settings → Security → Set up 2FA.
  3. Save your backup codes.

If you don't enroll by [date], you won't be able to sign in until you do.

Why we're doing this: admin accounts are high-value targets. 2FA blocks 
attacks that compromise just your password.

On this page