Entries tagged ‘widgets’
OAuth and Client-Side Widgets
posted January 21st, 2009, 2 comments, tagged javascript, oauth, security, widgets
Earlier this month, Christian Heilmann wrote about showing your Twitter data on any website, without your explicit permission. He’s posted a good overview of the matter on Ajaxian. His ‘hack’ relied on the user’s browser being logged in to the Twitter API, which allowed any website to call API methods.
In the work I’m currently doing for a London-based startup, providing a 3rd-party API and also providing widgets to be hosted on different sites is an important goal. But, as the Twitter example shows, there are security and privacy issues to be considered. In this post I’d like to focus on using OAuth to authenticate client-side widgets hosted on 3rd-party websites. I’m not at liberty to discuss the actual widgets we’d like to provide, so instead I’ll give a hypothetical example.
The HighScoreTracker Widget
Let’s imagine a web service named HighScoreTracker. They provide a widget that you can put on your own website, without requiring any server-side coding, which allows any website to send in high scores. This would allow users to track their high scores on various games on various websites. You run a gaming website named GamesForKicks and would like to participate in HighScoreTracker, so you place their widget on your site. After a visitor has finished playing the awesome game of RaceForBreaks, you want to automatically send their high score to HighScoreTracker.
When HighScoreTracker receives the high score, it needs to know two things:
- The game (website) that submitted the high score
- The user for whom the high score is submitted
This maps nicely to the OAuth use case. (For an introduction on OAuth, read the excellent Beginner’s Guide to OAuth). In OAuth speak, HighScoreTracker is the Service Provider. The widget placed on GamesForKicks is the Consumer and the user playing the game is the User.
With OAuth, the Consumer needs a token and a secret, which the Service Provider can use to verify the identity of the Consumer at a later point. Not only does the Consumer needs this token and secret, it needs to receive permission from the User as well, before it can act on her behalf.
As explained, the widget on GamesForKicks is the Consumer. Presumably you had to register with HighScoreTracker, which then generated the HTML code to display the widget on your site. For the widget to work as a proper Consumer however, it needs a consumer token and a secret. And that’s a problem: since the widget is embedded on GamesForKicks as an HTML snippet, both the token and the secret are public information. The secret is not a secret.
Verifying a Consumer without any secrets
If the secret is not a secret, that means anybody can act as if they’re GamesForKicks. Therefore a different way of verifying the Consumer’s identity is required. Luckily, an integral part of GamesForKicks’ identity is its web domain. If we can verify that the requests from the widget come from the domain we expect, we can verify the widget itself.
Here’s what we have so far:
- An OAuth Consumer: a widget running on a third-party website
- A consumer token, identifying the widget instance
- No consumer secret, since the widget is on a public page
- A known domain on which the widget is placed
The steps required by the OAuth protocol for a Consumer to be authorized to act on a Users behalf:
- Fetch a Request Token from the Service Provider
- Direct the User to the Service Provider for authentication, pass along the Request Token
- Once the Request Token is authorized, exchange it for an Access Token
- Use the Access Token to make requests to the Service Provider
We can validate the identity of the Consumer at steps 1 and 2. First of all, in order to fetch the Request Token, the widget loads a JavaScript file from the HighScoreTracker website:
<script src="http://highscoretracker.com/oauth/request-token.js?oauth_consumer_key=GamesForKicks&domain=gamesforkicks.com"></script>
(For brevity, I’m ignoring signed requests and other OAuth parameters)
HighScoreTracker verifies that the GamesForKicks Consumer is registered for gamesforkicks.com. It then returns a piece of JavaScript:
if(document.domain=='gamesforkicks.com') notify_request_token('123');
notify_request_token is a JavaScript function specified by the widget. It is invoked with the Request Token, but only if the widget is running on the expected domain.
Subsequently, the widget can open a popup window for the User to authorize the widget:
window.open('http://highscoretracker.com/oauth/authorize?oauth_consumer_key=GamesForKicks&token=123');
So far we haven’t seen anything that can’t be spoofed by an evildoer. For example, instead of fetching the Request Token using JavaScript, an evildoer could request it using a server side request and strip out the domain verification. Therefore we need to ensure that the Request Token was fetched using the User’s web browser.
We can ensure this by relying on the browser’s security model and cookies. First, if the Service Provider can ensure that there is no way to read the JavaScript file from a separate domain, we can be sure that if the Consumer has the Request Token, it either came from executing the JavaScript file, or from fetching it using a server side process. To make sure the Consumer executed the JavaScript file, the Service Provider can save a Verification Token in a browser cookie as the file is requested. When the authorization page is loaded, the Service Provider can verify that the specified Request Token corresponds with the Verification Token in the cookie. If this is the case, we can be sure that the only way the Consumer could have gotten hold of the Request Token is if the JavaScript executed normally, and therefore that the Consumer is running on the domain it is supposed to run on.
To summarize:
- The Consumer fetches the Request Token by loading a JavaScript file from the Service Provider
- This file is actually loaded through the web browser of the User
- The Service Provider generates the JavaScript file, which includes a domain verification, before passing along the Request Token to the Consumer
- It also saves a Verification Token in a browser cookie
- The value of the Verification Token is only known to the User’s browser, not to the Consumer
- The Consumer directs the User to an authorization page on the Service Provider
- The user’s browser loads this authorization page, and passes on the Verification Token in a cookie
- The Service Provider uses the Verification Token to verify the Request Token
- Because the only way to have a valid Request Token is to be on the correct domain, this verifies the Consumer identity
With the Consumer identity verified, the Service Provider can continue with the normal OAuth flow and authorize the Request Token. The Consumer can subsequently exchange its Request Token for an Access Token. Once the Consumer has the Access Token, it can be allowed access without further verification.
Essentially, the mechanism outlined here is a different verification algorithm on top of OAuth.
Preserving Access Tokens
One remaining problem is that OAuth Consumers typically preserve their Access Token for later use. This is problematic in our situation, because the identity of the Consumer is verified in the authorization phase. Instead, the Service Provider could provide a time-limited Access Token and provide for an automatic method of exchanging a Request Token for an Access Token if the User is logged in to the Service Provider and has previously authorized the Consumer. If the Request Token cannot be exchanged, the normal authorization process can continue.
In code, the widget fetches the Request Token:
<script src="http://highscoretracker.com/oauth/request-token.js?oauth_consumer_key=GamesForKicks&domain=gamesforkicks.com"></script>
When the browser fetches the JavaScript file, it also saves a Verification Token in a cookie.
The file is loaded, and the domain matches, so the widget now has a Request Token:
if(document.domain=='gamesforkicks.com') notify_request_token('987');
The widget attempts to exchange the Request Token for an Access Token right away:
<script src="http://highscoretracker.com/oauth/access-token.js?oauth_consumer_key=GamesForKicks&domain=gamesforkicks.com&token=987"></script>
The Service Provider verifies the Request Token using the Verification Token stored in the browser cookie. If the Request Token is verified, the User is already logged in to the Service Provider, and the Consumer has previously been authorized, it returns an Access Token:
if(document.domain=='gamesforkicks.com') notify_access_token('zyx');
If the User is not logged in, or the Consumer has not been authorized before, it tells the Consumer to follow the normal authorization route:
if(document.domain=='gamesforkicks.com') authorization_required();
Conclusion
In this post I outlined a technique for verifying the identity of an OAuth Consumer with a public key and secret, using standard browser security limitations. I’d love to discuss this approach further to see if this is a good idea in the first place, whether there are any vulnerabilities or weaknesses I’ve missed, and if this still jibes well with the OAuth approach.
