From OAuth 1.0 to OAuth 2.0
In the mid-2000s, a website that wanted something from another service usually asked for your password there. You typed your email password into a social network so it could find friends to invite, or your photo-sharing password into a printing site so it could fetch your pictures. The site then signed in as you, with every permission you had, for as long as it kept the password.
The companies running those APIs saw the same problem from the other side. They needed a way to let other applications act for a user without collecting the user's password, and they needed every application to do it the same way.
An open protocol for delegation
OAuth started around November 2006, while Blaine Cook was working on Twitter's OpenID support and looking for a way to delegate access to Twitter's API. Developers at Ma.gnolia, a bookmarking service, had a similar need. They found that several large providers had each built their own scheme, such as Google's AuthSub, Yahoo's BBAuth, and the Flickr API's authorization, but that no open standard existed. A small group began writing one in April 2007, opened the work to anyone interested that summer, and published OAuth Core 1.0 in December 2007.
OAuth 1.0 was a community specification, written by developers from many companies and published on the OAuth website rather than through a standards body. Its example will look familiar: a printing website fetching private photos from a photo-sharing site, without the person's password. Its vocabulary was different, though. The client was the consumer, the photo service was the service provider, and you were the user.
Signing every request
OAuth 1.0 gave each consumer a key and a secret at registration. After you approved access, the consumer also received a token and a token secret. With HMAC-SHA1, the signature method in the example below, it never sent either secret. Instead, it signed each request with both, and the service provider, which held copies, computed the same signature to check it.
All values in these examples are fictional. A signed request from the printer to the photo service looked like this:
GET /albums/42/photos HTTP/1.1
Host: api.photos.example
Authorization: OAuth oauth_consumer_key="photo-printer",
oauth_token="demo-oauth1-token",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1262304000",
oauth_nonce="demo-nonce-1",
oauth_signature="SOl5b3VkmEefUaZ%2BAdQOupVdNfA%3D"
To produce the signature, the consumer first built a signature base string: the HTTP method, the request URL, and every parameter except the signature, sorted, percent-encoded, and joined with ampersands:
GET&https%3A%2F%2Fapi.photos.example%2Falbums%2F42%2Fphotos&oauth_consumer_key%3Dphoto-printer%26oauth_nonce%3Ddemo-nonce-1%26oauth_signature_method%3DHMAC-SHA1%26oauth_timestamp%3D1262304000%26oauth_token%3Ddemo-oauth1-token
It computed HMAC-SHA1 over that string, using the consumer secret and token secret joined by & as the key, here demo-oauth1-consumer-secret&demo-oauth1-token-secret, and Base64-encoded the result. The timestamp, here 1 January 2010 at 00:00 UTC, and the nonce, a value the consumer never repeated with the same timestamp, let the provider refuse a captured request sent a second time.
The design had real strengths. With HMAC-SHA1 no request carried either secret, so a request sent without TLS did not reveal them, and a request altered in transit failed verification. The token secret crossed the network only once, in the response that issued it, and that response needed TLS. Its practical difficulty was the base string. Consumer and provider had to build exactly the same string, byte for byte, including how they encoded spaces, ordered repeated parameters, and wrote the port. A mismatch produced a bare "invalid signature" with no hint about which byte differed.
The 2009 flaw and Revision A
Obtaining a token took three steps. The consumer requested a temporary request token, sent your browser to the service provider to approve it, and then exchanged the approved request token for an access token and secret. In OAuth 1.0, the consumer named its callback address in the browser redirect, and the final exchange needed nothing except the request token.
In April 2009, the OAuth community published a security advisory describing a session fixation attack on that flow. An attacker started a connection at the printer from their own printer account and stopped before approving. They sent you the photo service's authorization link, which contained their request token. If you followed it and approved, that request token was now approved for your photos, while the printer still associated it with the attacker's session. Because the callback address traveled in the link, the attacker could even point it somewhere harmless so that your browser never returned to the printer. The attacker then went back to the printer, which exchanged the request token and connected your photo library to the attacker's printer account.
OAuth Core 1.0 Revision A, published in June 2009, closed the gap with two changes:
The consumer now sent oauth_callback in its first, signed request, and the provider confirmed that it understood the new rules with oauth_callback_confirmed=true. The callback could no longer be changed in the browser. After approval, the provider sent a new value, oauth_verifier, back through the browser that did the approving, and the consumer had to present it to exchange the request token. In the attack, the verifier would go to your browser rather than to the attacker, so the attacker's session could not finish the exchange.
The idea outlived the protocol. In OAuth 2.0, the authorization code reaches the client only through the browser that approved, much as the verifier did. The opposite attack, where an attacker's response is pushed into your browser, is what state and PKCE address, as Request correlation and CSRF showed.
In April 2010, the IETF published Revision A, with corrections, as RFC 5849, The OAuth 1.0 Protocol. It is an Informational RFC: it records the community protocol rather than making it an IETF standard.
Designing OAuth 2.0
By then OAuth 1.0 was widely deployed, and its limits were clear. Signing was hard to get right. Every consumer needed a secret or private key of its own, which suited websites but not JavaScript in a browser or apps installed on phones, where a credential packaged into every copy could be extracted. A single service provider both issued and accepted tokens, which fitted one website better than a large provider with many APIs. And the core specification defined no lifetime for access tokens, so many lasted until the user revoked them.
A proposal called OAuth WRAP, from engineers at several large providers, took a different approach: short-lived bearer tokens sent over TLS, with a separate refresh token for continued access. The IETF's OAuth working group combined ideas from OAuth 1.0 and WRAP, and in October 2012 published RFC 6749, The OAuth 2.0 Authorization Framework, with RFC 6750 defining how bearer tokens are sent. RFC 6749 obsoletes RFC 5849 and is deliberately not backward compatible with it.
| Question | OAuth 1.0 | OAuth 2.0 |
|---|---|---|
| How is a request protected? | Each request is signed with the consumer and token secrets. | TLS protects the connection, and the access token is a bearer token. |
| Who issues and accepts tokens? | One service provider does both. | An authorization server issues them, and resource servers accept them. |
| How is access obtained? | One redirect-based flow for obtaining a token. | A framework of grant types, extensible with new ones. |
| How does access continue? | Access tokens with no defined lifetime. | Short-lived access tokens and optional refresh tokens. |
| What about clients that cannot keep a secret? | No defined approach. | Public and confidential client types. |
The move from signatures to bearer tokens was among the most debated changes. It made OAuth far easier to implement, since a token in an Authorization header over HTTPS needs no canonical string. It also meant that a copied token works for whoever holds it, which is why so many later protections aim to keep tokens from leaking or to make a leaked one useless. DPoP and mutual TLS, covered in Advanced OAuth, later brought proof of possession back in a different form.
The framework approach drew criticism too. In mid-2012, a few months before publication, the specification's lead editor, who had also edited OAuth 1.0, resigned and withdrew his name from it, arguing that the framework left too many decisions to implementers to be secure or interoperable on its own. Much of the work since, from the security best current practice to profiles such as FAPI, has narrowed those decisions. Two choices RFC 6749 offered have since been withdrawn by current guidance: The implicit grant and The password grant look at each.