Portable MFA bundle for Silverstripe 5 with TOTP (Google Authenticator) and WebAuthn (security keys/biometrics) support.
composer require restruct/silverstripe-mfa-bundle- Add
SS_MFA_SECRET_KEYto.env, rundev/build
That's it. MFA is enforced with a 6-month grace period out of the box.
- Bundles
silverstripe/mfa,silverstripe/totp-authenticator, andsilverstripe/webauthn-authenticator - Configurable TOTP settings (issuer name, period, algorithm)
- WebAuthn configured to allow both biometrics (Touch ID) and security keys
- Admin interface to reset/remove user MFA methods
- Sensible defaults: requires at least 1 MFA method
- Silverstripe ^5.0
- PHP ^8.1
ext-bcmathPHP extension (required by WebAuthn)
composer require restruct/silverstripe-mfa-bundleAdd to your .env:
SS_MFA_SECRET_KEY="your-secure-random-key-here"Generate a key:
php -r "echo bin2hex(random_bytes(32));"Create app/_config/mfa.yml:
# Settings via this bundle's extension
Restruct\MFABundle\Extensions\TOTPConfigExtension:
issuer: 'My Company CMS' # Name shown in authenticator apps
period: 30 # Seconds per code (default: 30)
algorithm: 'sha1' # sha1, sha256, sha512 (default: sha1)
# Settings on SilverStripe's TOTP classes (set directly)
SilverStripe\TOTP\Method:
code_length: 6 # Number of digits (default: 6)
SilverStripe\TOTP\RegisterHandler:
secret_length: 16 # Length of generated secret (default: 16)
user_help_link: 'https://example.com/mfa-help' # Help link during setupBy default, this bundle allows both platform authenticators (Touch ID, Windows Hello, Face ID) and cross-platform authenticators (USB security keys). You can restrict this:
SilverStripe\WebAuthn\RegisterHandler:
# null = both types (default set by this bundle)
# 'platform' = biometrics only (Touch ID, Windows Hello)
# 'cross-platform' = security keys only (SilverStripe default)
authenticator_attachment: ~
# Custom help link shown during setup
user_help_link: 'https://example.com/webauthn-help'Note: WebAuthn requires HTTPS and a supported browser (Chrome, Firefox, Safari, Edge).
This bundle automatically enables "MFA Required" on first dev/build and hides the SiteConfig MFA settings. Users will be prompted to set up MFA on their next login.
A grace period of 6 months is set by default, allowing users to skip MFA setup temporarily. After the grace period expires, MFA becomes mandatory.
# Customize grace period (default: 180 days = 6 months)
Restruct\MFABundle\Extensions\SiteConfigMFAExtension:
grace_period_days: 90 # 3 months
# grace_period_days: 0 # No grace period - MFA required immediatelyTo show the MFA settings in SiteConfig (for manual control):
Restruct\MFABundle\Extensions\SiteConfigMFAExtension:
show_mfa_settings: trueAdd to .env:
BYPASS_MFA=1| Class | Setting | Default | Description |
|---|---|---|---|
SiteConfigMFAExtension |
grace_period_days |
180 | Days users can skip MFA (0 = no grace period) |
SiteConfigMFAExtension |
show_mfa_settings |
false | Show MFA fields in SiteConfig |
TOTPConfigExtension |
issuer |
SiteConfig Title | App name shown in authenticator |
TOTPConfigExtension |
period |
30 | Seconds per code |
TOTPConfigExtension |
algorithm |
sha1 | Hash algorithm (sha1/sha256/sha512) |
| Setting | Class | Default | Description |
|---|---|---|---|
code_length |
Method |
6 | Number of digits (6-8) |
secret_length |
RegisterHandler |
16 | Secret key length |
| Setting | Class | Default | Description |
|---|---|---|---|
authenticator_attachment |
RegisterHandler |
null* |
Allowed authenticator types |
*This bundle sets the default to null (allow both). SilverStripe's default is cross-platform (security keys only).
| Class | Setting | Default |
|---|---|---|
SilverStripe\TOTP\RegisterHandler |
user_help_link |
/mfa-help/totp |
SilverStripe\WebAuthn\RegisterHandler |
user_help_link |
/mfa-help/webauthn |
SilverStripe\MFA\Authenticator\LoginHandler |
user_help_link |
/mfa-help/ |
SilverStripe\MFA\BackupCode\RegisterHandler |
user_help_link |
/mfa-help/backup-codes |
Override these if you use a custom URL segment or external help pages.
Authenticator attachment options:
~ornull: Both platform and cross-platform (recommended)'platform': Only biometrics (Touch ID, Windows Hello, Face ID)'cross-platform': Only USB/NFC security keys
| Setting | Class | Default | Description |
|---|---|---|---|
required_mfa_methods |
EnforcementManager |
1 | Minimum methods required |
- A secret is generated and encrypted with
SS_MFA_SECRET_KEY - The QR code shows your configured issuer name
- User scans with Google Authenticator, Authy, 1Password, etc.
- On login, user enters the 6-digit code from their app
- User registers their authenticator (USB key, Touch ID, etc.)
- A credential is stored, tied to your domain
- On login, user taps their key or uses biometrics
Note: WebAuthn credentials are domain-specific. Not recommended with silverstripe/subsites as each subsite domain would need separate credentials.
Admins with the MFA_ADMINISTER_REGISTERED_METHODS permission can manage MFA for other users:
- Go to Security → Users and edit a user
- Find the Registered MFA Methods GridField (only shown for users with MFA configured)
- Delete any MFA methods to force the user to re-register
When all MFA methods are removed, the user will be prompted to set up MFA again on their next login.
Note: This GridField only appears when viewing other users' accounts, not your own (use the standard MFA interface for self-management).
SilverStripe MFA also includes a built-in "Send account reset email" button that sends the user a link to reset both their password and MFA settings. This is useful when you want the user to verify their identity via email.
The SS_MFA_SECRET_KEY environment variable is not set.
- Ensure you're using HTTPS (required by WebAuthn)
- Check browser support (Chrome, Firefox, Safari, Edge)
- Verify
authenticator_attachmentisn't set to'cross-platform'if you want biometrics
The default SilverStripe setting only allows USB security keys. This bundle changes it to allow both, but if you've overridden authenticator_attachment, ensure it's set to ~ (null) or 'platform'.
Use BYPASS_MFA=1 in .env to skip MFA during development.
Note: WebAuthn passkeys require a trusted HTTPS certificate. Self-signed certificates may cause registration to fail with Chrome's native passkey storage.
WebAuthn supports different authenticator types with different trade-offs:
| Type | Examples | Pros | Cons |
|---|---|---|---|
| Platform | Touch ID, Face ID, Windows Hello | Free, built-in, convenient | Tied to single device |
| Cross-platform | YubiKey, USB/NFC security keys | Portable, works anywhere | Requires purchasing hardware |
Modern browsers support passkeys - WebAuthn credentials that sync across devices:
- iCloud Keychain: Syncs across Apple devices (Mac, iPhone, iPad)
- Google Password Manager: Syncs across Chrome browsers logged into same Google account
- Password managers: 1Password, Bitwarden, Proton Pass can store passkeys
When a user registers WebAuthn with authenticator_attachment: ~ (both), the browser offers choices:
- This device (platform) - Touch ID/Face ID, may sync via iCloud/Google
- Security key (cross-platform) - USB/NFC hardware key
- Phone/tablet - Use another device via QR code
Users can register multiple authenticators for redundancy.
This bundle defaults to authenticator_attachment: ~ (allow both) for maximum flexibility. Users can choose based on their needs:
- Office workers with one machine → Touch ID
- Mobile workers → Synced passkey or hardware key
- High-security environments → Hardware keys only (
'cross-platform')
This bundle includes translatable help pages served at /mfa-help/:
| URL | Content |
|---|---|
/mfa-help/ |
Overview of MFA |
/mfa-help/totp |
Authenticator app setup |
/mfa-help/webauthn |
Security key / biometrics setup |
/mfa-help/backup-codes |
Backup codes explanation |
Help pages automatically display in the logged-in user's CMS locale (from Member.Locale), falling back to the site's default locale. Translations are included for: English, Dutch, German, French, and Spanish.
To disable the built-in help pages entirely, set DISABLE_MFA_HELP as an environment variable or PHP constant:
# .env
DISABLE_MFA_HELP=1Or in app/_config.php:
define('DISABLE_MFA_HELP', true);To serve help pages at a different URL (e.g., /hulp/):
- Disable the default route via
DISABLE_MFA_HELP - Configure your custom URL segment and Director rule:
# app/_config/mfa-help.yml
Restruct\MFABundle\Controllers\MFAHelpController:
url_segment: 'hulp'
SilverStripe\Control\Director:
rules:
'hulp': 'Restruct\MFABundle\Controllers\MFAHelpController'- Update the help URLs to match your custom segment:
SilverStripe\TOTP\RegisterHandler:
user_help_link: '/hulp/totp'
SilverStripe\WebAuthn\RegisterHandler:
user_help_link: '/hulp/webauthn'
SilverStripe\MFA\Authenticator\LoginHandler:
user_help_link: '/hulp/'
SilverStripe\MFA\BackupCode\RegisterHandler:
user_help_link: '/hulp/backup-codes'Override the translations via lang files in your project, or point to your own URLs:
SilverStripe\TOTP\RegisterHandler:
user_help_link: 'https://your-site.com/help/totp'
SilverStripe\WebAuthn\RegisterHandler:
user_help_link: 'https://your-site.com/help/security-keys'
SilverStripe\MFA\Authenticator\LoginHandler:
user_help_link: 'https://your-site.com/help/mfa'
SilverStripe\MFA\BackupCode\RegisterHandler:
user_help_link: 'https://your-site.com/help/backup-codes'To disable help links entirely, set them to empty strings.
- MFA Module - Core MFA framework
- TOTP Authenticator - Authenticator app configuration
- WebAuthn Authenticator - Security keys configuration
- WebAuthn Guide - Introduction to WebAuthn
- Authenticator Selection Criteria - Technical details on authenticator filtering
MIT