After my article on the browser sync mechanisms I spent some time figuring out how Firefox Accounts work. The setup turned out remarkably complex, with many different server types communicating with each other even for the most basic tasks. While this kind of overspecialization probably should be expected given the scale at which this service operates, the number of different authentication methods is surprising and the official documentation only tells a part of the story while already being fairly complex. I’ll try to show the entire picture here, in case somebody else needs to piece it all together.
Authentication server login: password hash
Your entry point is normally accounts.firefox.com. This is what Mozilla calls the Firefox Accounts content server – a client-side only web application, backed by a very basic server essentially producing static content. When you enter your credentials this web application will hash your password, currently using PBKDF2 with 1000 iterations, in future hopefully something more resilient to brute-forcing. It will send that hash to the Firefox Account authentication server under api.accounts.firefox.com and get a session token back on success.
Using the session token: Hawk with derived ID
Further communication with the authentication server uses the Hawk authentication scheme that carefully avoids sending the session token over the wire again and signs all the request parameters as well as the payload. A clever trick makes sure that the client doesn’t have to remember an additional Hawk ID here: the ID is a hash of the session token. Not that the content server communicates a lot with the authentication server after the login, the most important call here is signing a public key that the content server generates on the client side. The corresponding private key can then be used to generate BrowserID assertions.
Do you remember BrowserID? BrowserID a.k.a. Persona was a distributed single sign-on service that Mozilla introduced in 2011 and shut down in 2016. Part of it apparently still lives on in Firefox Accounts. How are these assertions being used?
Getting OAuth token: BrowserID assertion
Well, Firefox Accounts use the BrowserID assertion to generate yet another authentication token. They send it to oauth.accounts.firefox.com and want an OAuth token back. But the OAuth server has to validate the BrowserID assertion first. It delegates that task to verifier.accounts.firefox.com which forwards the requests to browserid-local-verify package running on some compute cluster. Verification process involves looking up issuer’s public key info and verifying its RSA signature. If everything is right the verifier server will send information contained in the assertion back and leave it up to the OAuth server to verify that the correct issuer was used. Quite unsurprisingly, only “api.accounts.firefox.com” as issuer will give you an OAuth token.
Funny fact: while the verifier is based on Node.js, it doesn’t use built-in crypto to verify RSA signatures. Instead, this ancient JS-based implementation is currently being used. It doesn’t implement signing however, so the RSA-Sign library by Kenji Urushima is used on top. That library is no longer available online, and its quality is rather questionable.
Accessing user’s profile and subscription settings: OAuth
OAuth is the authentication method of choice when communicating with the profile.accounts.firefox.com server. Interestingly, the user’s profile stored here consists only of the user’s name and their avatar. While the email address is also returned, the profile server actually queries the authentication server behind the scenes to retrieve it, using the same OAuth token.
The content server will also use OAuth to get the user’s newsletter subscription settings from the Basket proxy living under accounts.firefox.com/basket/. This proxy will verify the OAuth token and then forward your request to the basket.mozilla.org server using an API key to authenticate the request. See, the Basket server cannot deal with OAuth itself. It can only do API keys that grant full access or its own tokens to manage individual accounts. It isn’t exactly strict in enforcing the use of these tokens however.
Accessing sync data: Hawk with tokens
An additional twist comes in when you sync your data which requires talking to token.services.mozilla.com first. The stated goal of this service isn’t merely assigning users to one of the various storage servers but also dealing with decentralized logins. I guess that these goals were formulated before BrowserID was shut down. Either way, it will take your BrowserID assertion and turn it into yet another authentication token, conveniently named just that: token. The token is a piece of data containing your user ID among other things. This data is signed by the token server, and the storage servers can validate it.
Mozilla goes a step further however and gives the client a secret key. So when the storage server is actually accessed, the Hawk authentication scheme mentioned before is used for authentication: the token is used as Hawk ID while the secret key is never sent over the wire again and is merely used to sign the request parameters.
Clearly, some parts of this setup made sense at some point but no longer do. This especially applies to the use of BrowserID: the complicated generation and verification process makes no sense if only one issuer is allowed. The protocol is built on top of JSON Web Tokens (JWT), yet using JWT without any modifications would make a lot more sense here.
Also, why is Mozilla using their own token library that looks like a proprietary version of JWT? It seems that this library was introduced before JWT came along, today it is simply historical ballast.
Evaluating the use of Hawk is more complicated. While Hawk looks like a good idea, one has to ask: what are the benefits of signing request parameters if all traffic is already encrypted via TLS? In fact, Hawk is positioning itself as a solution for websites where implementing full TLS protection isn’t feasible for some reason. Mozilla uses TLS everywhere however. Clearly, nothing can help if one of the endpoints of the TLS connection is compromised. But what if an attacker is able to break up TLS-protected connections, e.g. a state-level actor? Bad luck, Hawk won’t really help then. While Hawk mostly avoids sending the secret over the wire, this secret still needs to be sent to the client once. An attacker who can snoop into TLS-encrypted connections will intercept it then.
In the end, the authentication zoo here means that Mozilla has to maintain more than a dozen different authentication libraries. All of these are critical to the security of Firefox Accounts and create an unnecessarily large attack surface. Mozilla would do good by reducing the authentication methods to a minimum. OAuth for example is an extremely simple approach, and I can see only one reason why it shouldn’t be used: validating a token requires querying the OAuth server. If offline validation is desirable, JWT can be used instead. While the complexity is higher then, JWT is a well-established standard with stable libraries to support it.