mozilla / fxa-oauth-server

OAuth server for Firefox Accounts
49 stars 40 forks source link

Report MFA status to reliers at the end of the OAuth dance #519

Closed rfk closed 6 years ago

rfk commented 6 years ago

To make informed security decisions, reliers need to know whether an OAuth flow was completed using MFA and may want to inspect the details of what kind of MFA took place. We need to decide on an API for exposing this data, most likely as part of the OIDC "id_token".

First action here is for @rfk to review OIDC and related specs for prior art on this topic.

rfk commented 6 years ago

Depends on https://github.com/mozilla/fxa-auth-server/issues/2315 for getting this information back from the auth-server.

rfk commented 6 years ago

Useful context: list of "authentication method reference" values for OAuth: https://tools.ietf.org/html/draft-ietf-oauth-amr-values-04

rfk commented 6 years ago

(In case it wasn't obvious, I'm just going to brain-dump notes to myself into this issue until we come up with a concrete proposal)

From the literature of OAuth-adjacent standards, the relevant terms of art here are:

The OpenID Connect spec allows reliers to provide an acr_values parameter when they initiate the OAuth dance, to tell the IdP what acr values are preferred. It also defines "acr" and "amr" claims to be included in the id_token to reflect the properties of the completed login flow, and since the id_token is signed, this allows the relier to trust that they accurately reflect what happened during the login flow.

So my high-level proposal is basically:

For bonus points, we can allow the relier to specify an "essential acr claim" which will cause us to error out during the auth flow if it can't be satisfied. But that feels unnecessary for MVP.

rfk commented 6 years ago

RFC 6711 governs the registration namespace for well-known LoA strings that can be used as acr values, but doesn't actually define any of its own.

This spec adds one called "mod-mf" for multi-factor authentication, although it may be intentionally mobile-carrier-as-IdP-specific:

http://openid.net/specs/openid-connect-modrna-authentication-1_0.html

rfk commented 6 years ago

RFC 6711 governs the registration namespace for well-known LoA strings that can be used as acr values, but doesn't actually define any of its own.

This spec adds one called "phr" for "phishing resistant", which I don't think we can meet with an OTP approach, but might be useful when we add support for webauthn:

https://openid.net/specs/openid-connect-eap-acr-values-1_0.html

rfk commented 6 years ago

NIST has a bunch of literature on this that I haven't finished digesting, and defines levels of assurance from "0" (worst) to "4" (best)

These levels are probably still useful, but I believe they're superseded by the shiny new "Digital Identity Guidelines" documents here:

https://pages.nist.gov/800-63-3/

rfk commented 6 years ago

Interestingly, 800-63-3 section 2.5.3 notes that they have explicitly removed "email" as a valid channel for out-of-band authenticators, which might affect what we can standards-compliantly-claim as the assurance level for our email-loop verification flow.

rfk commented 6 years ago

Section 5.1.2 of https://pages.nist.gov/800-63-3/sp800-63b.html#sec5 uses the term "lookup secret" to encompass what we refer to as "recovery codes". It defines them as "something you have" which makes them suitable as a second factor for AAL2.

rfk commented 6 years ago

Section 5.1.2.2 has some interesting requirements around the use of lookup secrets, including the requirement that they SHALL be hashed for storage. /cc @vbudhram.

rfk commented 6 years ago

Section 5.1.4.1 has some interesting requiremnts for single-factor OTP devices, of which TOTP is one. In particular it requires that we SHALL only accept a TOTP code once. We don't have to comply with these requirements of course, but we should be aware of them and consider them.

rfk commented 6 years ago

In summary, https://pages.nist.gov/800-63-3/sp800-63b.html is a long but excellent read, has lots of advice that's relevant to what we're doing with 2FA, and its "Authenticator Assurance Levels" are a nice fit for the acr values we want to use here.

rfk commented 6 years ago

So, here's a concrete proposal that tries to hew closely to existing standards and specs without getting too lost in the weeds.

Let's use the list of amr values from RFC 8176 where they meet our needs, and invent similar ones where they don't. Some examples:

Let's use acr values that map to the Authenticator Assurance Levels of SP 880-63B as follows:

The process for a relier to require MFA during a login flow would then look like this:

  1. Start the OAuth dance by visiting: https://oauth.accounts.firefox.com/authorize?client_id=foobar&scope=openid+profile&acr_values=AAL2
  2. Receive the authorization code in the redirect at the end of the OAuth dance.
  3. Exchange the authorization code for an access_token and id_token
  4. Validate the id_token and extract the acr and amr claims
  5. Check that that acr claim contains "AAL2" as requested
  6. Optionally, inspect the amr claim for further info about the authentication process

@vbudhram what do you think? The amr values here could map directly onto the verificationMethod values from the sessionTokens table in the db, while the oauth-server could maintain an internal table to map combinations of amr values onto an acr value.

/cc @gdestuynder @linuxwolf it would be great to get your input on this if you have a few moments.

rfk commented 6 years ago

Validate the id_token and extract the acr and amr claims

I'll note that this is a signed token, so the relier should ideally validate the signature before accepting the claims therein. But given that they just fetched the token directly from https://accounts.firefox.com over a secure channel, then in the interests of simplicity, they could probably get away with just parsing the claims out of it without checking the signature...

rfk commented 6 years ago

Authing with amr: ["pwd", "email"] will produce this acr, since according to SP880-63B, these are both "something you know" auth factors.

If we wanted, we could invent an intermediate acr that would allow reliers to request this combination, but we shouldn't call it AAL2 because SP880-63B says very explicitly that it isn't.

vbudhram commented 6 years ago

@rfk Nice summary! Here are my thoughts

requirements around the use of lookup secrets, including the requirement that they SHALL be hashed for storage.

It shouldn't be an issue to hash the lookup secrets (recovery codes). However, the messaging to the user will definitely need to change to let them know that they can't see these codes again.

Authing with amr: ["pwd", "email"] will produce this acr, since according to SP880-63B, these are both "something you know" auth factors.

It seems a little strange to me that verifying with a password and verifying with a password and email, gives the same assurance level (AAL1). I agree that we should have some intermediate acr to signify these differences, maybe AAL1+ or AAL1b.

rfk commented 6 years ago

It shouldn't be an issue to hash the lookup secrets (recovery codes). However, the messaging to the user will definitely need to change to let them know that they can't see these codes again.

*nod*. FWIW we're not bound to follow any particular detail in that document, it's just a very nice collection of best practices.

It seems a little strange to me that verifying with a password and verifying with a password and email, gives the same assurance level (AAL1).

Yeah. On the other hand, we know that if you can auth with ["email"] then you can also auth with ["pwd", "email"] by virtue of doing a password-reset loop, so they are equivalent in some sense. I wouldn't mind deferring that until we have a concrete ask for it from a relier (maybe AMO?) at which point it should be pretty trivial to add.

gdestuynder commented 6 years ago

On many services we (Mozilla IAM/Auth0) equate the value of a password with the value of the email access due to the reason @rfk mentioned (password reset flow through email verification), though not always, if the service does not allow resetting through email.

We also validate the id_token and would extract amr then reason about the logic ourselves unless there is any benefit to using acr instead (and checking acr >= AAL2, which is somewhat annoying with a string anyway)

In other words, sounds good to me / would work for us. In particular, we'd request AAL2 by default so in most cases the users won't get an error message from us "asking to enable 2FA" which is nice, and if our logic of what we'd consider an "AAL2 equivalent" is different from yours, we'd still fail with a message, but that would probably be a rare case.

rfk commented 6 years ago

(and checking acr >= AAL2, which is somewhat annoying with a string anyway)

Yeah, trying to do anything other than an == comparison on these strings sounds like it could get awkward fast.

we'd request AAL2 by default so in most cases the users won't get an error message from us "asking to enable 2FA" which is nice

To clarify - if you request AAL2 , and the user doesn't have 2FA enabled on their account, what behavior would you expect from FxA? Do you want us to return an error saying we couldn't meet the requested level, or succeed at a lower level and let you check that and show your own messaging? (Noting that you'd have to do the check anyway for security reasons).

gdestuynder commented 6 years ago

To clarify - if you request AAL2 , and the user doesn't have 2FA enabled on their account, what behavior would you expect from FxA? Do you want us to return an error saying we couldn't meet the requested level, or succeed at a lower level and let you check that and show your own messaging? (Noting that you'd have to do the check anyway for security reasons).

I expect FxA would come up with some UI, such as "This application/relier requests you to authenticate with Firefox Accounts using strong authentication/2FA. Please setup 2FA in order to use this application/relier. If you do not, you will not be able to access it." (with much better words)

This is nicer, because it gives the user a chance to setup 2FA, then click ok and actually login - vs hitting an error and having to go to FxA site, setup 2FA, then try again (painful)

rfk commented 6 years ago

I like it; also a good opportunity to drive some more users to enabling 2FA :-)