Security Analysis
OAuth2 is (relatively) standardized and used in large sites like Google and Github, so it's been studied a lot (which is the biggest reason for using it over a home-grown solution). This section merely describes the few items that jumped to mind during our early analysis.
Why client_id is included in the first redirect (1.2)
The IdP has two jobs: to make sure the browser is being driven by the right human, and to ask that human whether it's ok to give the RP power over some aspect of their account.
To ask that question properly, the IdP must somehow describe the RP (to the human). client_id
lets the IdP present an RP/application name and logo (both pre-registered by the RP) during that question. In this respect, client_id
serves the same purpose as the Persona siteName
string and siteLogo
image, which are displayed in the Persona login box. client_id
is not validated at the time of the first redirect (i.e. site#1 can direct the browser to ask the user to grant permission to site#2), however the requirement to submit client_secret
when actually fetching the token means that spoofing a site cannot actually yield power for the instigator.
The IdP remembers client_id
with the tokens it allocates, making it useful in a subsequent dashboard which displays all outstanding tokens, to tell the user which token is for which RP.
In OAuth2, client_id
serves two other purposes. First, it gives the IdP a measure of control over RP applications: at any time, the IdP can simultaneously revoke all tokens generated for the RP (and reject new ones). The threat of this revocation may discourage abusive behavior by the RP.
Second, it enables whitelisting of specific clients. In particular, Mozilla services could automatically be granted certain authority over the account: e.g. Marketplace could be allowed to have profile/read
access without explicit user consent, so it can display a human name on its pages. The fxa-oauth-server needs to know which client is asking for permission to decide whether the request should be whitelisted or not.
Why "state" is included in the first redirect (1.2)
This binds the RP's outbound redirect with the response redirect that delivers a code. It prevents a session-fixation attack.
Without it, an attacker could begin the flow, signing into their own IdP account, then stop the process (at step 3.2) before submitting the resulting code to the RP. This code, when eventually delivered to the RP, will empower the session that submits it to control the attacker's account at that RP, as well as access whatever resources the OAuth2 token provides.
Then the attacker presents the code-bearing return-redirect URL to an unsuspecting victim, in a context where they expect to sign into the same RP. When the victim follows the link, their session will be quietly signed into the attacker's RP account. Any information they reveal to the RP at that point may be retrievable later (e.g. sent email) or otherwise useable by the attacker (e.g. credit cards stored by a merchant site for "one-click" purchases).
Adding and checking "state" binds two things together: the user's original attempt to log into the RP, and the subsequent submission of "code" to the RP (which implements the login), which prevents this attack.
This kind of attack was originally discovered in OAuth1.0, however in a different sort of flow.
Why "redirect_uri" must be pre-registered
If a malicious RP ("Evil-RP") is allowed complete control over the redirect_uri
(submitted as a query argument in 1.2, used by the return redirect in 3.1), then Evil-RP can request a login with a client_id
of its choosing (a benign-sounding app "good-RP", or one which the user has probably already authorized), but attach a redirect_uri
pointing to Evil-RP, allowing it to learn a code meant for the other app. It then pretends to be the user while talking to good-RP, skipping the 1.2 - 3.1 login process entirely, and submitting the other code to message 3.2 . Good-RP will then validate this code with the oauth server, it will check out, and Good-RP will grant access (over the user's account information at Good-RP) to the attacker (i.e. to the session which submitted 3.2).
By limiting redirect_uri
to a pre-configured value (in fact completely ignoring any query argument), codes will only be revealed to the right RP. The specific binding is between the RP name displayed to the human during the permission-grant page (keyed by client_id
) and the RP server which learns a code that will be valid when submitted by that human-selected RP.
Why client_secret is included in the fxa-oauth-server "verify" message (3.3)
I'm not yet sure. I think state
and the pre-registered redirect_uri
are sufficient to keep codes secret (only revealed to the right parties). Codes are unguessable, so the only way to get a code that will validate is for it to come from the fxa-oauth-server in the first place. Requiring that the code be submitted along with the RP client_id should be sufficient to assure the RP that the code was meant for them and not for someone other RP. Including client_secret
may provide additional "belt-and-suspenders" security margin.
One thing it does do is to distinguish between the legitimate user and the RP server that they've authorized. It isn't clear when it would be useful to make this distinction: the user nominally has more power over their own account than the RP does. But there may be some environments where this makes sense (no-backend all-web applications? native apps on locked-down smartphone OSes?).
The final access_tokens that come back in 3.4 are labelled with a particular client, but not actually bound to them: anybody who learns the token can use it, even other RPs. So it is important to make sure they're only granted to the right RP (which can then choose to share it if they see fit).
The most likely attack would be 3.3, where attacker EvilRP (who somehow knows a code that was allocated to GoodRP) submits it along with GoodRP's (public) client_id. If this is accepted, EvilRP will be given oauth tokens that were meant for GoodRP. EvilRP might get this from a collusion with the legitimate user (who has control over these resources anyways), or through some bug that reveals GoodRP's code.
Fundamentally, it's necessary to make the user-permission question "real", by enforcing the relationship between the RP named in the question, and the RP that eventually gets the power. There are two things that make the "right" RP special: the client_secret
and the pre-configured redirect_uri
.
There may be flows in which redirect_uri
is not used (native apps?), and thus does not protect the code against leaks that would allow EvilRP to learn it.