Read this first — public beta
This is version 0.1. It has been built carefully and tested thoroughly, but it has been tested on one firewall, by one person. It changes how you log in to the box that guards everything else you own. Treat it accordingly.
Keep a second browser signed in as an administrator while you set this up, and do not enable enforcement until you have signed in with your second factor in that other browser and it worked. If you lock yourself out anyway, there is a console recovery path below that does not need the web interface — read it before you need it.
Please report anything that surprises you. A 2FA plugin that fails in an interesting way is a plugin nobody should be running.
os-webauthn 0.1 · OPNsense 26.1 · FreeBSD 14 amd64 · free for non-commercial use
You want both. The library is what makes passkeys possible; without it the one-time password half still works and passkeys simply are not offered.
Why this exists
OPNsense cannot do passkeys at all. The one box standing between your network and the rest of the internet is protected by a password, and at best a six-digit code typed from a phone — while every laptop, phone and password manager you already own has been able to do unphishable hardware-backed authentication for years.
The built-in one-time password support is also minimal by design: one setting, applied to
everything at once. Turn it on for the web interface and you have turned it on for
sudo, for SSH password logins and for the physical console too
— including for the accounts you were relying on to get back in.
So this plugin does two things. It adds passkey sign-in, and it replaces the one-time password path with an implementation written against RFC 6238 from scratch — with replay protection, a drift window you can actually configure, and a separate setting for each way into the box.
What you get
- Passkey sign-in — YubiKey and other security keys, Touch ID, Face ID, Windows Hello, 1Password, iCloud Keychain. Anything that speaks WebAuthn. The private key never leaves the authenticator and there is nothing to phish, because the credential is bound to your firewall's domain name.
- One-time passwords that behave — codes cannot be replayed once used. The tolerance for clock drift is a real setting, expressed in time steps, and it is asymmetric: you can accept a code that arrives slightly late without also widening the window into the future. SHA-1, SHA-256 or SHA-512; six, seven or eight digits.
- Per-service control —
sudo, SSH passwords and the console each get their own setting: inherit the web interface, password only, or always require a code. Decide your own threat appetite instead of accepting one switch for all four. - Enforcement that actually enforces — with it on, an account that has a second factor cannot sign in with a password alone. That sounds obvious. It is worth stating, because a second factor you can skip is decoration.
- Self-service enrolment — users enrol their own keys under their own privilege. Administrators can see, rename, suspend and revoke every credential, but cannot enrol a passkey on somebody else's behalf, because the authenticator has to be in that person's hands.
- Emergency recovery — a root console command and an API endpoint behind the administrative privilege, either of which removes a user's second factor without touching their password. Every removal is written to the audit log.
- Nothing secret leaves the box — no cloud service, no callback, no
telemetry. Passkey public keys and settings live in
config.xmlwhere they belong; the counters that change on every login live in a separate state database, so your configuration history stays readable and restoring a backup cannot roll a replay counter backwards.
Before you start
- OPNsense 26.1 on FreeBSD 14, amd64.
- A real domain name, over HTTPS. This is a WebAuthn rule, not ours: a
passkey is bound to a registrable domain, so
https://firewall.example.comworks andhttps://192.168.1.1can never work. The one-time password half has no such requirement and is happy on an IP address. - The domain must not change. A passkey is permanently tied to the domain it was created for. Change it later and every enrolled passkey stops working, with no way to convert them — everyone re-enrols.
Install
Two packages: the plugin, and the WebAuthn library it uses. Install the library first. Copy both to the firewall, then as root:
The library package is optional in the strict sense — leave it out and the one-time password half still works perfectly well, the plugin simply detects that it cannot do passkeys and does not offer them. Install it if you want passkeys, which is presumably why you are here.
Nothing needs restarting and no reboot is required. The new pages appear under
System → Access immediately.
Quickstart
-
Switch passkeys on
Go to
System → Access → 2FA Settings, open the Passkeys tab, tick Enabled and set the domain to the name you reach the web interface by — the same name in the address bar, withouthttps://and without a port. -
Enrol your own passkey
Go to
System → Access → 2FA. This page is self-service — it is where every user sets up their own second factor, and it is the only page most of them ever need. Click to add a passkey, give it a name you will recognise later, and approve the prompt from your key, phone or password manager.Enrolment asks your authenticator to sign in once before it stores anything. A credential that registers but cannot actually sign in is caught here, rather than the next time you try to log in.
-
Test it, in a different browser
Sign out there, or use a private window, and sign in with the passkey. Keep this session open while you do it. If it does not work, you have lost nothing.
-
Add one-time passwords, if you want them
Same
2FApage. Scan the QR code with your authenticator and type a code back to confirm — nothing is stored until a code you generated has been verified, so a half-finished enrolment leaves your account exactly as it was.Worth doing even if you have a passkey: a passkey cannot be presented over SSH, so a one-time password is what gets you in at a shell prompt.
-
Decide what each way in requires
On
2FA Settings, under Other ways in, setsudo, SSH passwords and the console independently. At a shell prompt there is no separate box for the code, so it is typed straight after the password with no space — worth knowing before you require one. -
Then, and only then, require codes
The switch at the bottom of
2FA Settingsmakes a second factor mandatory for the web interface. Press it last, with another browser already signed in and tested.
If you lock yourself out
Get to the physical or serial console, or SSH in with a key — key-based SSH never asks for a second factor — and drop to a root shell. Then:
That removes the second factor and nothing else — the password
is untouched, the account stays exactly as it was, and the removal is written to the
audit log. Add --otp or --passkeys to
remove just one kind.
There is an equivalent API endpoint for the same job, behind the administrative privilege, for when you can reach the API but not the login page.
Uninstall
FreeBSD has called this "deinstall" since before it was called FreeBSD, and
pkg is not going to change its mind now:
Your configuration is left alone and so are the enrolled credentials, so reinstalling picks up exactly where you left off. Removing the plugin also removes its authenticators, so every login falls straight back to whatever OPNsense did before — password only, unless you had configured something else yourself.
The state database at /var/db/webauthn is deliberately kept. It
holds the replay counters, and wiping it on every uninstall would reset that floor each
time. To remove it properly, once you are sure:
Updating
For the beta, updates are manual — download the new package and
pkg add -f it over the top. Your configuration, your enrolled
passkeys and your replay counters all survive.
The firewall's own System → Firmware page will not
offer you an update, and that is expected rather than broken: it only checks package
repositories, and this is installed from a file. Watch this page, or the Discord, for new
versions.
Licence
Free for non-commercial use, under the PolyForm Noncommercial License 1.0.0. That covers home and personal use, hobby and study, charities, schools, public research, public safety and health organisations, and government — for those, it is simply free, with no registration and nothing to sign.
Running it at a business is a commercial purpose and needs a separate licence. Get in touch — for a beta the answer is likely to be pleasant.
The licence covers this plugin only. The WebAuthn library it uses is lbuchs/WebAuthn, which is MIT licensed and packaged separately.