---
title: On WebAuthn, MFA, and local SSO
date: 2023-09-28
tags: [security, code]
description: Multi factor authentication (MFA) is gaining steam. Time for me to take a step back and get an overview.
---

Multi factor authentication (MFA) is gaining steam. The ecosystem is evolving
and laws such as
[NIS-2](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=CELEX%3A32022L2555)
start making it a legal requirement. At the same time the ecosystem is becoming
so diverse that two people working on the topic might work with completely
different terminology and software.

Time for me to take a step back and get an overview. I will try not to do the
old "passwords are bad and MFA is better" dance. I will still do the
comparison, but only skim the boring parts and quickly move on to the
(hopefully) more interesting details.

## Passwords

Passwords have been used for authentication for a long time, even though they have many issues:

-   Humans pick passwords that are easy to guess
-   Humans easily forget their passwords, so they keep them on sticky notes on their screen
-   Passwords are typed in, so they can be extracted by keyloggers
-   Passwords are sent over the wire, so they can be extracted if there is insufficient transport encryption
-   Passwords are sent to servers, so the server operators can get access to your other accounts if you reuse passwords (or type in the wrong password)
-   Passwords are stored on the server, so they can be extracted from database leaks if they are not sufficiently hashed and salted

In short: Passwords are in too many places.

## Two Factor Authentication (2FA)

Now, replacing passwords all at once didn't seem realistic. Also, all the
alternatives we had available came with issues of their own. So instead of
replacing passwords, we started adding other authentication mechanisms on top.

## One-Time Passwords

A very common approach to two factor authentication is to send one-time
passwords over a side channel such as
[email](https://notes.xoxco.com/post/27999787765/is-it-time-for-password-less-login),
SMS, or some proprietary smartphone app. These side channels are often insecure
though.

A better approach is to use [Time-Based One-Time Passwords
(TOTP)](https://www.rfc-editor.org/rfc/rfc6238): A one-time password is
generated based on the current time and a pre-shared symmetric secret. The
secret is commonly transferred via QR codes. The main downsides here is that
phishing is easy: Attackers can just call users and say "I need you to verify
your identity by telling me the current code" and they are in. Also, the
symmetric secrets cannot be hashed on the server.

## Something you own

Many people say that the two factors in "two factor authentication" are
knowledge and possession. Sometimes they also add biometrics ("something you
are"). I don't buy that. Computer science is built on the idea that everything
is data. All these factors will eventually collapse into bits, aka
"something you know".

A common implementation of possession is that websites store a long lived
cookie on a device. This device is now "trusted". Logging in from an untrusted
device is then blocked or at least a warning is sent to the user. This process
of course needs to be bootstrapped somehow.

This simple cookie-based implementation assumes that session cookies are
short-lived and trust cookies are long-lived. Users who never log out do not
get any benefits. And users who regularly delete all cookies get confusing
warnings ("login from new device"). So I am not a fan.

More involved implementations use something like a trusted platform module
(TPM) where a secret is stored but can never be extracted. Knowledge of the
secret is replaced by possession of the TPM. As far as I know, real-world TPMs
are not perfect. I fear we might get into trouble if our crypto protocols start
to depend on TPMs. Instead, we should think of them as obstacles. They make
attacks harder, but don't give any guarantees.

## Multi Factor Authentication (MFA)

My impression is that the term "2FA" is slowly being displaced by "MFA". The
"multi" is not so much about "two or more factors", but more about a move to a
more open understanding of authentication. Since we started thinking about 2FA,
the ecosystem has evolved and we are slowly ready to leave passwords behind.
The discussion is no longer just about amending passwords with a second factor,
but about finding new and better ways to do authentication in general.

This also means that we are in a time of change, kinda post-password. We
already know that passwords are a thing of the past, but we have not yet
settled on a new approach.

## Single Sign-On (SSO)

Let's shift gears for a moment and think about SSO.

With SSO we get a third party: You (via your *client*) want to log in to a
website (*relying party*), but the actual authentication is done by an
*identity provider*. This has two implications:

-   Everything is centralized to a single login, so you can concentrate on
    making that really secure, e.g. by picking a more complex password or some
    elaborate MFA scheme.

-   The communication between the relying party and the identity provider is
    completely automated, so it can use more complicated but stronger
    mechanisms (long random passwords, challenge-response, asymmetric
    cryptography, …).

## Local Identity Providers

If the identity provider is running on your devce, the authentication doesn't
need to involve the network, which significantly reduces the attack surface.

One example of this idea are password managers. They deliver on the first
aspect (a single place that can be really secure) but not on the second (a
password is still sent to the relying party). Their main benefit is that they
are fully backwards compatible with existing websites.

Your SSH keys also act as local identity providers. Compared to password
managers, they use a much better protocol to communicate with the relying
party.

## WebAuthn / FIDO2

[WebAuthn](https://www.w3.org/TR/webauthn/) is a new JavaScript interface. Even
though it is often talked about in the context of MFA, I believe it is more
productive to understand it as a way to authenticate against a special kind of
local identity providers called "authenticators".

It was created by the [Fast IDentity Online (FIDO)
Alliance](https://fidoalliance.org/). The commonly used term "FIDO2" is (as far
as I understand) not a specific standard, but an umbrella term for "WebAuthn
and related stuff".

I finally got around to read the WebAuthn spec and I was a bit shocked. You
want your security related specs to be clear and specific. WebAuthn is not
that. Let me illustrate that with a quote:

> A Client-side discoverable Public Key Credential Source, or Discoverable
> Credential for short, is a public key credential source that is
> ***discoverable*** and usable in authentication ceremonies where the Relying
> Party does not provide any credential IDs, i.e., the Relying Party invokes
> `navigator.credentials.get()` with an ***empty*** `allowCredentials`
> argument. This means that the Relying Party does not necessarily need to
> first identify the user.
>
> — <https://www.w3.org/TR/webauthn/#client-side-discoverable-credential>

I will get to discoverable credentials later, but suffice to say that I did not
understand them from reading this paragraph. (It is actually even worse because
they changed the wording mid-way, so all the interfaces use the term "resident
keys" instead.)

Looking past the overly complex jargon, there is a lot of good stuff there. I
will skip over most of it because many posts have already been written about
that. I will only say that it is a lot like SSH in that it uses public key
cryptography to communicate between the authenticator and the relying party and
that it has some other benefits (e.g. use of TPMs) that even put it ahead of
SSH in terms of security.

## Client-side MFA

So far we have discussed that WebAuthn is a form of local SSO. It can be used
as a building block for MFA either by using it as one of several authenticaion
steps on the relying party, or by requiring user verification on the
authenticator. Let's look at the latter option.

How user verification is implemented depends on the authenticator. Another
benefit of using a local identity provider is that it has access to a wide
range of sensors, so apart from passwords (or PINs), we also see facial
recognition or fingerprints.

The relying party can select whether user verification should be discouraged,
preferred, or required. It is preferred by default. Discouraged user
verification is useful to avoid redundant verification if the relying party has
already asked for a password. (Redundant user verification could become a real
usability issue as we add MFA in more and more places.)

Required user verification is only useful if the relying party can trust the
authenticator. There is a a certificate based
["attestation"](https://developers.yubico.com/WebAuthn/Concepts/Securing_WebAuthn_with_Attestation.html)
system for that, but a relying part must jump through some additional hoops to
use it. If you are a relying part and absolutely depend on user
verification, make sure to check attestation.

Apart from user verification, authenticators can also check user presence.
However, relying parties have no control over that and it is always required.
That means that authentication without user interaction is not possible.

## Moving beyond passwords

As explained in the beginning, the goal is to get rid of passwords completely.
The buzzword a year ago was "passwordless", the current iteration is "passkey".
Those are just marketing terms that represent more less clearly defined
configurations of the technologies described so far.

Maybe a more interesting development is to also skip usernames. This is where
we come back to discoverable credentials:

Many authenticators have limited storage capacity, so they mainly keep a single
secret. The private keys are encrypted using that secret and stored with the
relying party. Now if I want to log in, I have to send my username so the
relying party can pass the correct encrypted key. It could also send me all the
keys, but that is neither secure nor practical.

If, on the other hand, the key is stored on the authenticator, all that is not
necessary. The relying party would send a challenge, my authenticator would
use the local key to generate a reply, and I would be logged in.

(With this explanation, maybe go back and see if you can understand the
paragraph I quoted above.)

Now, this comes with some usability bumps. For one, the relying party would
have to know that my authenticator supports discoverable credentials and
provide a legacy login otherwise. I guess that is the main reason why we hardly
see this in the wild. The other issue is that having multiple accounts at
the same relying party becomes a bit more complicated. Either I have different
authenticators, or the authenticator allows me to pick one, or the relying
party receives all keys and asks me which one to use.

## Recovery

Before ending this post, we also have to look into some potential issue. The
first one is recovery.

There are many ways to lose access to your account. Something you know may be
forgotten, something you own may be stolen or just break, and even your
fingerprints can be lost (e.g. by losing a finger).

The common way to tackle this issue, in the terminology we established so far,
is to require `n` factors, but provide more than `n` factors.

The most common version of this is that you can send a "reset password" link to
your email account. The two factors here are a password and access to an email
account. It is tempting to think of this as a completely separate recovery
flow. But in practice this just means that either factor is sufficient. And the
security of your account depends on the weakest one.

With multi factor authentication, the most common issue is that recovery
collapses into a single factor, e.g. it is possible to reset all factors using
the same side channel or recovery keys are stored in the same password manager
as the password. If that is the case, you are not doing MFA and not gaining any
security. So make sure that your recovery options are solid!

## Account enumeration attacks

When we are thinking about password-less approaches we also have to consider
some attacks that have been solved for passwords a long time ago.

Early on in my career I learned that I should display the same warning on a
failed login attempt regardless of whether the username exists or not. This is
so that attackers cannot find out whether a specific user has an account.

In WebAuthn (and if not using discoverable credentials) the relying party will
usually respond with a set of keys for a given username. How can we avoid
enumeration in that case? The
[spec](https://www.w3.org/TR/webauthn/#sctn-username-enumeration) recommends
creating a fake key in that case, but that sounds like there could be all kinds
of footguns.

## Conclusion

After reading and hearing a lot about MFA, I had to structure all that
information. I believe the idea to think of WebAuthn as local SSO can be
productive.

Even though MFA has come a long way, it is still too soon to say what kind of
authentication we will settle on.
