cherubimro / roundcube-fido2
FIDO2/WebAuthn two-factor authentication for Roundcube Webmail
Package info
github.com/cherubimro/roundcube-fido2
Type:roundcube-plugin
pkg:composer/cherubimro/roundcube-fido2
Requires
- php: >=8.1
- lbuchs/webauthn: ^2.2
- roundcube/plugin-installer: >=0.1.3
Requires (Dev)
None
Suggests
None
Provides
None
Conflicts
None
Replaces
None
README
A Roundcube Webmail plugin that adds FIDO2/WebAuthn second-factor authentication using hardware security keys.
Users log in with their password first, then verify with a security key tap as a second factor. Supports YubiKey, Feitian BioPass, SoloKeys, Google Titan, and any FIDO2-compliant authenticator.
Requirements
- Roundcube 1.6+
- PHP 8.1+
- HTTPS (WebAuthn requires a secure context)
- Composer
Installation
Via Composer (recommended)
From your Roundcube directory:
composer require cherubimro/roundcube-fido2
If the package isn't on Packagist yet, add the repository first in your Roundcube composer.json:
"repositories": [ { "type": "vcs", "url": "https://github.com/cherubimro/roundcube-fido2" } ]
Manual installation
Copy the plugin into your Roundcube plugins/ directory as webauthn:
cd /path/to/roundcube cp -r /path/to/roundcube-fido2 plugins/webauthn cd plugins/webauthn composer install --no-dev
Copy the example configuration and edit it:
cp config.inc.php.dist config.inc.php
At minimum, set the relying party ID to match your domain:
$config['webauthn_rp_id'] = 'mail.example.com';
Enable the plugin in Roundcube's config/config.inc.php:
$config['plugins'] = ['webauthn', /* other plugins */];
The database table is created automatically on first use.
Configuration
All settings go in plugins/webauthn/config.inc.php:
| Setting | Default | Description |
|---|---|---|
webauthn_2fa_policy |
optional |
off / optional / required |
webauthn_rp_name |
Roundcube |
Display name shown to users during ceremonies |
webauthn_rp_id |
auto-detect | Domain name (e.g. mail.example.com). Must match the domain users access Roundcube on. Set it: left empty it is taken from the request's Host header. An IP address cannot be a relying party ID, and a ceremony on one is refused |
webauthn_rp_origins |
[] |
Allowed origins array (e.g. ['https://mail.example.com']). Empty = auto-detect |
webauthn_timeout |
60000 |
Ceremony timeout in milliseconds |
webauthn_user_verification |
preferred |
preferred / required / discouraged |
webauthn_attestation |
none |
none / indirect / direct |
webauthn_attachment |
'' |
'' (any) / platform / cross-platform |
Policy modes
- off - Plugin disabled entirely.
- optional - Users choose whether to enable 2FA in Settings > Security Keys. This is the default.
- required - Every account must have a key and use it. A user with no key is held at Settings > Security Keys until one is registered; nothing else in the webmail is reachable until then, and registering the key counts as that session's verification.
Usage
Registering a key
- Log in to Roundcube.
- Go to Settings > Security Keys.
- Click Register New Key.
- Enter a name (e.g. "My YubiKey 5") and tap/touch your security key when prompted.
- If policy is
optional, toggle Enable two-factor authentication.
Logging in with 2FA
- Enter your username and password as usual.
- A verification page appears: "Please insert and tap your security key."
- Tap your key. On success, you are redirected to your inbox.
Managing keys
- Register multiple keys for redundancy.
- Delete keys from the settings page.
- When policy is
required, the last key cannot be deleted.
Database
The plugin creates a webauthn_credentials table automatically. SQL schemas for manual creation are in the SQL/ directory:
SQL/mysql.sqlSQL/postgres.sqlSQL/sqlite.sql
Upgrading
To 1.0.2
This release changes when the second factor is demanded, so sessions that are already open behave differently after the upgrade:
- A session that was opened before the user registered a key, or before the
policy was set to
required, is now asked for the key on its next request. Previously it kept full access for its whole lifetime, including the ability to switch two-factor authentication off again. - Under
required, an account with no key is held at the enrolment page instead of being let through. - If Roundcube is reached on an IP address, ceremonies now fail with a clear
message instead of binding credentials to a name that cannot work. Serve it
on a domain name and set
webauthn_rp_id. - On PostgreSQL, registering a key works for the first time. Raw bytes cannot
be quoted into a statement for a
byteacolumn, so every registration used to fail with a syntax error; binary is now written as hex and read back from the stream PostgreSQL hands over.
No database change is needed.
Security
- The gate runs on every request through the
startuphook, and it asks for a positive marker left by a completed ceremony. It does not rely on a flag set at login time, so a session that Roundcube'slogin_afterhook never saw — one opened before enrolment, or by a plugin that logs users in itself — is held like any other. - What the gate requires is recomputed on every request, so registering a key, changing the policy or flipping the preference takes effect on sessions that are already open.
- While a session is held, only the page it is held at and that page's own ceremony endpoints are reachable. A held session cannot register a key of its own, delete a key, or turn two-factor authentication off.
- The session ID is replaced when the second factor is accepted, so the ID that existed before verification no longer grants access.
- Challenges are single-use (consumed immediately after verification).
- CSRF protection via Roundcube's built-in request token mechanism on every endpoint, including the one that issues an assertion challenge.
- Clone detection: a signature counter that does not advance is refused, the credential is flagged, the event is logged, and the stored counter keeps the highest value seen so the next attempt cannot clear the flag by itself. The flag is shown next to the key in Settings.
- All database queries are scoped to the authenticated user ID.
- Credential IDs are stored as raw binary and compared byte-for-byte.
- A ceremony is refused unless the request's host belongs to the configured
relying party, and to one of
webauthn_rp_originswhen those are set. - Webmail is not the only way in: a second factor here does not protect direct IMAP, POP3 or SMTP access with the same password. Restrict those separately.
Supported databases
MySQL/MariaDB, PostgreSQL and SQLite, with matching schemas in SQL/. The
tables are created on first use. Verified on MariaDB 11.4, PostgreSQL 16 and
SQLite, with Roundcube 1.7.4.
Library
Uses lbuchs/webauthn v2.2 -- a zero-dependency PHP WebAuthn server library.
License
GPL-3.0-or-later (same as Roundcube).