Demystifying Social APIs: OAuth2 & Simple XHR 2
This article will give you insights into the inner workings of the proprietary JavaScript libraries that many of us include in our Web projects. Social sharing buttons and federated authentication, which are found in the likes of the Live Connect JavaScript API and Facebook JavaScript SDK, are just two examples you may have come across.
In this article, you’ll learn about the OAuth 2.0 approach to user authentication, using XMLHttpRequest 2 for cross-origin resource sharing (CORS) and also REST. At the end, I’ll demonstrate a working app that allows users to connect to and manipulate their SkyDrive photos in the browser.
Getting Started
About two years ago, I was asked to add Windows Live and Facebook Connect buttons to a Web site.
Adding these buttons to a Web page required two libraries, one from each of the providers, plus a little JavaScript to wire them up. Both libraries had some magic that made them work, although I doubted that all of the 200 KB of JavaScript I wrote was being used. Before I was asked to implement a third service, I opened up Fiddler and started to inspect what was going over the wire. After a little poking around, I found my way to the docs, and before I knew it I had the premise for an insightful article. So, get a cup of tea and a biscuit and enjoy the read.
A Glossary of Terms
When we talk of wiring up a Web app with other Web services, it’s useful to first familiarize yourself with the characters.
The app (otherwise known as the client) is your Web application or a Web site you use. The user is the end user who uses your app. The provider is the Web service that your app is going to connect to—for example, Windows Live or Facebook. The authorization server is the provider’s user-login service.
The Technologies
Two common industry standards are used to authenticate users and securely sign subsequent API requests: OAuth 1.0 and OAuth 2.0. The implementations of these underlying technologies do not differ, but the URLs and nuances between providers do. As such, many providers have their own JavaScript library to support their API, outlined in Table 1.
Provider: OAuth Version
Windows Live API: 2
Facebook Graph: 2
Google AP:I 2
Twitter: 1.0a
Yahoo: 1.0a
LinkedIn: 1.0a
Dropbox: 1.0
Table 1. API Technologies Used by Popular Social Sites
This article focuses on OAuth 2.0—and don’t be confused by the name. OAuth 2.0 and OAuth 1.0 are vastly different protocols. Furthermore, OAuth 1.0 has been deprecated by many Web services in favor of OAuth 2.0.
OAuth2: Authentication
Here’s how OAuth.net describes OAuth2: “An open protocol to allow secure authorization in a simple and standard method from web, mobile and desktop applications. . . . OAuth is a simple way to publish and interact with protected data. It’s also a safer and more secure way for people to give you access. We’ve kept it simple to save you time.â€
I think of OAuth2 as an authentication mechanism that lets an app obtain an access token for a user, based on the provider’s Web service. The app can then use this access token to query or modify the provider’s data on behalf of the user.
Initiate OAuth 2
Initiating the authentication process begins with opening a new browser window to a special URL on the provider’s Web site. Here the user is prompted to sign in and agree to share certain functionalities with the application. The process is illustrated in Figure 2, where the provider is https://a.com and the client is http://b.com/. Look at the URLs in the address bar, where you should see access_token in the final window. Figure 3 shows an example of a sign-in window from Windows Live. In the figure, the application adodson.com is asking for access to SkyDrive photos and documents.
Figure 2. OAuth2 Flow
Figure 3. OAuth 2 Consent Screen Hosted by Windows Live
The URL in Figure 3 is:
https://oauth.live.com/authorize?client_id=00001111000&scope=wl.photos&response_type= token&redirect_uri=http://b.com/redirect.html
This special URL is made up of an initial path for the authorization page and four required key-value parameters:
- A client_id provisioned by the provider when the app owner registers the app. (Register yours for Windows Live at https://manage.dev.live.com/.)
- The scope, which is a comma-separated list of strings that denote which services the app can access. I maintain a list of possible scopes for various providers at http://adodson.com/hello.js/#ScopeandPermissions.
- The response_type=token attribute, which translates to “Hey, return the access token immediately.”
- The redirect_uri attribute, which is the address for where to redirect the window after the user has signed in or cancelled. This URL must belong to the same origin as the client_id when it was provisioned.
There’s also an optional state parameter, which is a string that, when it’s included, is simply returned in the response from the authentication provider.
Receiving the Access Token
After the user has authenticated and consented to share with the app, the browser window is redirected to the page defined in the redirect_uri parameter. For example:
http://adodson.com/graffiti/redirect.html#access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cyt
Appended to the URL location hash (#) are some credentials:
- access_token A unique string that can be used to query the provider’s API.
- expires_in A number (in seconds) that access_token is valid for.
- state The string that can be optionally passed into the state parameter and returned.
The credentials can be read relatively easily using the window.location object. For example, the access token can be extracted like so:
var access_token = (window.location.hash||window.location.search).match(/access_token=([^&]+)/);
After obtaining the access token, the next step is to use it.
OAuth2 History
OAuth 2.0 was devised in 2010 by some smart people at Microsoft and Facebook as a means to securely share data services with other applications on behalf of a user. It does this in a manner that need not rely on a server or complicated cryptic algorithms beyond SSL.
Since its inception, OAuth2 has become the de facto method with which third-party apps authenticate their users via Windows Live or Facebook and then tap into and share data with these megalithic data warehouses. The standard has since proliferated through Google’s services, LinkedIn and SalesForce, and Twitter has tweeted its interest. As you can see, OAuth2.0 comes highly endorsed.
Native Apps
An alternative parameter for response_type=token is response_type=code. Using “code” prompts the provider to return a short-lived authorization code instead of an access token. The code is used in conjunction with the client secret (allocated at the time of registering the app), and the application must then make a server-to-server call to obtain the access token. This approach gets around domain restrictions that are imposed on redirect_uri, yet it ensures it’s the same app. Using “code†is therefore required when working with native apps that are domainless. Using the server-side authentication flow is different from the pure client flow described in this article, but it is still part of OAuth2. You can read about it in greater detail at IETF-OAuth2.
Cross-Origin Resource Sharing (CORS)
The application, having successfully obtained the access token, is now capable of making signed HTTP requests to the provider’s API.
Accessing resources on one domain from another is known as cross-origin resource sharing, or CORS. Doing this is not as simple as accessing content from the same domain. Consideration must be taken to comply with the same-origin policy imposed by the browser. Such a policy applies conditions on scripts seeking to access content outside their current browser window’s domain name and port number. If the conditions are not met, the browser will throw a SecurityError exception.
XHR2
The new incarnation of the JavaScript API, XMLHttpRequest 2 (XHR2), supports the ability to use CORS. There are two parts to enabling this capability: in the client, the request must use the XHR2 interface, and the server must respond with an Access-Control-Allow-Origin header.
Client JavaScript
The following code illustrates an HTTP request in the client using XHR2:
var xhr = new XMLHttpRequest(); xhr.onload = function(e){ // contains the data console.log(xhr.response); }; xhr.open('GET', “http://anotherdomain.comâ€); xhr.send( null );
Access-Control HTTP Headers
The provider responds with an Access-Control-Allow-Origin header, satisfying the security policy in the user’s browser. For example, an HTTP request URL to the Windows Live API might create the following HTTP request and response:
REQUEST GET https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cy ... RESPONSE HTTP/1.1 200 OK Access-Control-Allow-Origin: * ... { "id": "ab56a3585e01b6db", "name": "Drew Dodson", "first_name": "Drew", "last_name": "Dodson", "link": "http://profile.live.com/cid-ab56a3585e01b6db/", "gender": "male", "locale": "en_GB", "updated_time": "2012-11-05T07:11:20+0000" }
The browser security policy does not reject this CORS request because the provider allowed it by providing the HTTP header Access-Control-Allow-Origin: *. The asterisk (*) wildcard character indicates that all HTTP requests from any Web application are allowed to read the response data from this Web service.
All the social sign-in providers that I’ve looked at—for example, the Live Connect API and Facebook’s Graph API—do, of course, return this header in their responses.
XHR 2 Browser Support
Since Cross origin resource sharing widely available in all browsers yet a good method to shim this up is to revert to the omnipresent Javascript Object Notation with Padding (JSONP) solution. JSONP simply gets round the security issues of Cross Domain by calling the API via the ‘src’ attribute of an embedded script tag.
All good API’s like SkyDrive API will ‘pad’ their Javascript Object response with a function call if the “callback†parameter is provided in the URL.
First though we can feature detect by snooping for a property of the new XHR interface, like in the example below.
If( “withCredentials†in new XMLHttpRequest() ){ // browser supports XHR2 // use the above method } else { // Use JSONP, add an additional parameter to the URL saying return a callback jQuery.getJSON(url + '&callback=?', onsuccess); }
The above code relies on jQuery’s getJSON method as fallback, and does a great job too.
REST: Representational State Transfer
Up to this point you’ve learned about authenticating users via the industry standard OAuth2 and cross-origin resource sharing with XMLHttpRequest and Access-Control headers. Next, I’ll cover what are essentially access to and interaction with servers and data sets on the Web.
In the code in the previous section, you saw a simple HTTP request and response. This is not unlike how HTML pages and their assets are served. However, when performed within an application to interoperate with Web services, we instead refer to this mechanism as a Representational State Transfer, or REST.
To sign a REST request with an access token, merely include the token within the query string parameters, as in this example:
https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3C
Connecting the Dot Coms
Now, with the technology and terminology covered so far, let’s crack on with a demonstration of an app that puts all this theory to the test. A short while ago, I created a photo-editing app called Graffiti (see Figure 4). I felt it was a perfect contender for a social makeover so that users can load their photos from SkyDrive onto the canvas element and manipulate their online photos in the browser. You can see the demo at http://adodson.com/graffiti/ and also check out the code at https://github.com/MrSwitch/graffiti/.
In the app, I’ve re-created some of the functions in the SkyDrive JavaScript SDK, such as WL.login, WL.filePicker and WL.api(). If you are not familiar with these methods, don’t worry, because I’ll explain what they do as we go.
Figure 4. Graffiti App with Photos from a SkyDrive Album
Essentially, the new functionality includes these items:
- getToken() Authenticates a user and stores the user’s access token for interacting with SkyDrive. This is akin to the WL.login() function.
- httpRequest() For querying the SkyDrive API and getting results so that a navigation can be built, such as in Figure 4. This is akin to WL.api and WL.filePicker.
Let’s look at each in more detail.
getToken: Authenticate
The Graffiti’s authentication process is designed to work on demand. When a user’s action requires a signed API request, the authentication process begins.
In the SkyDrive API, the authentication handler is WL.login. The following code includes a custom function (getToken) that re-creates this method. It’s applied throughout the Graffiti app code and precedes any API requests, just like its counterpart. You can see a typical invocation illustrated here:
btn.onclick = function(){ getToken("wl.skydrive", function(token){ // … do stuff, make an API call with the token }); }
The getToken function, shown in the following code, keeps track of the stored token and triggers the authentication flow when authorization is required. The received tokens are persisted for subsequent calls via the new HTML5 localStorage feature, which is available in modern browsers and allows
developers to read and write persisted information (in this case our auth-token data) via key-value pairs.
Initially no tokens exist, so window.authCallback is assigned to the callback and is invoked when the access token is available. The window.open method creates a popup to the provider’s authorization page. Replace the text “WINDOWS_CLIENT_ID†with your app ID.
function getToken(scope, callback){ // Do we already have credentials? var token = localStorage.getItem("access_token"), expires = localStorage.getItem("access_token_expires"), scopes = localStorage.getItem("access_scopes") || ''; // Is this the first sign-in or has the token expired? if(!(token&&(scopes.indexOf(scope)>-1)&&expires>((new Date()).getTime()/1000))){ // Save the callback for execution window.authCallback = callback; // else open the sign-in window var win = window.open( 'https://oauth.live.com/authorize'+ '?client_id='+WINDOWS_CLIENT_ID+ '&scope='+scope+ '&state='+scope+ '&response_type=token'+ '&redirect_uri='+encodeURIComponent (window.location.href.replace(/\/[^\/]*?$/,'/redirect.html')), 'auth', 'width=500,height=550,resizeable') ; return; } // otherwise let’s just execute the callback and return the current token. callback(token); }
The getToken function doesn’t work all on its own. After the user has consented, the pop-up browser window is returned to the redirect.html page with the new access token in the path. This HTML document is shown in the following code.
<!DOCTYPE html> <script> var access_token = (window.location.hash||window.location.search).match(/access_token=([^&]+)/); var expires_in = (window.location.hash||window.location.search).match(/expires_in=([^&]+)/); var state = (window.location.hash||window.location.search).match(/state=([^&]+)/); if(access_token){ // Save the first match access_token = decodeURIComponent(access_token[1]); expires_in = parseInt(expires_in[1],10) + ((new Date()).getTime()/1000); state = state ? state[1] : null; window.opener.saveToken( access_token, expires_in, state ); window.close(); } </script>
The full Web address to the redirect.html page contains the access token, state and expiry arguments. The script in the redirect.html page (shown earlier) extracts the arguments from the window.location.hash object using a regular expression before passing those back to the parent window object (window.opener) by calling a custom function, saveToken. Finally, this script executes window.close() to remove the pop-up window because it’s no longer needed. Here’s the code for saveToken:
function saveToken(token, expires, state){ localStorage.setItem("access_token", token ); localStorage.setItem("access_token_expires", expires ); // Save the scopes if((localStorage.getItem("access_scopes") || '').indexOf(state)===-1){ state += "," + localStorage.getItem("access_scopes") || ''; localStorage.setItem("access_scopes", state ); } window.authCallback(token); }
The saveToken function stores the access_token credentials in localStorage. Finally, the callback saved at window.authCallback is triggered.
Pretty neat, huh? This lengthy code replaces the WL.login function of the Live Connect JavaScript API. The OAuth2 flow is a little intense and confusing at first, but I think that once you see it in action, you’ll appreciate it better.
Next, let’s re-create the way we query the SkyDrive API.
httpRequest: Query SkyDrive
The Graffiti app also requires that a user be able to query SkyDrive and pick a file to draw onto the canvas. The WL.filpicker method is the SkyDrive JavaScript API’s equivalent. However, the filePicker is a UI method, whereas a REST call to SkyDrive is typically handled by the WL.api method. (Figure 4 illustrates Graffiti’s filePicker-esq UI.)
I created two functions to separate the HTTP request process from the UI. In the following code, the function httpRequest emulates the WL.api(‘get’,..) method:
function httpRequest(url, callback){ // IE10, FF, Chrome if('withCredentials' in new XMLHttpRequest()){ var r = new XMLHttpRequest(); // xhr.responseType = "json"; // is not supported in any of the vendors yet. r.onload = function(e){ callback(JSON.parse(r.responseText}); } r.open("GET", url); r.send( null ); } else{ // Else add the callback on to the URL jsonp(url+"&callback=?", callback); } }
The httpRequest function initially tests for the presence of XHR2 by detecting whether the property withCredentials exists within an instance of the XHR API. The fallback for browsers that don’t support XHR2 cross-origin capabilities is JSONP (check out jQuery.getJSON).
The xhr.onload handler converts the response string to a JavaScript Object and passes it as the first parameter to the callback handler. The httpRequest function is easily initiated.
httpRequest(“https://apis.live.net/v5.0/me?access_token=EwA4Aq1DBAAUlbRWyAJjK5w968Ru3Cyâ€, callback);
The function that calls httpRequest and subsequently puts the thumbnail images on the screen is createAlbumView, and it’s this method that re-creates the WL.filePicker-like functionality, for example:
createAlbumView("me/albums", "SkyDrive Albums");
Here’s the code for createAlbumView:
function createAlbumView(path, name){ // Get access_token from OAuth2 getToken("wl.skydrive", function(token){ // Make httpRequest // Retrieve all items from path defined in arguments httpRequest('https://apis.live.net/v5.0/'+path+'?access_token='+token, function(r){ // Create container // … // Loop through the results for(var i=0;i<r.data.length;i++){ // Create thumbnail and insert into container createThumbnail(r.data[i], container); } }); }); }
When provided with a path name of an album (such as “me/albumsâ€), createAlbumView populates the navigation screen with the items found at that address. While the initial list of albums is available at “me/albumsâ€, createAlbumView is recursive. Items it finds that are albums form the new path, and hence make the whole of SkyDrive navigable. The following code shows how the item exposes its type and the different way it is handled by the app:
function thumbnail_click (item){ if( item.type === "photo" ){ applyRemoteDataUrlToCanvas( item.source ); } else if(item.type === "album"){ createAlbumView(item.id+'/files', item.name); } }
Items that are Images are returned straight into Graffiti’s canvas element.
Signing Out
This article has aimed to demystify magic that is packaged up in the proprietary JavaScript libraries. You’ve seen three functions that imitate those from the SkyDrive’s JavaScript API.
* getToken emulates WL.login
* httpRequest emulates WL.api(‘get’,…)
* createAlbumView emulates WL.filePicker()
Using the SkyDrive JavaScript SDK was just an example. Facebook Connect JavaScript SDK and others work in a very similar way. Perhaps now you can see these libraries for what they are; a collection of adoptive technologies and clever tricks.
This story isn’t over. There are more ways that XMLHttpRequest can be leveraged. In Part 2, I’ll introduce those and illustrate them by extending the Graffiti app to edit albums, uploading the Graffiti art work to SkyDrive and sharing information on the users’ activity feed. Magnifico!
Until then, if you would like to support a project that aggregates many social APIs on the Web, please take a look at http://adodson.com/hello.js/ and share your thoughts on the GitHub page.
Thanks for reading.
References
- Graffiti source code
- OAuth 2 Intro
- Windows Live Connect API
- XMLHTTPRequest Object
- Detecting support for XHR2
- SkyDrive API
- HelloJS Library
- HelloJS Source
This article is part of the HTML5 tech series from the Internet Explorer team. Try-out the concepts in this article with three months of free BrowserStack cross-browser testing @ http://modern.IE
About the Author
Andrew Dodson is a Web technology aficionado who gets rude over EcmaScript, HTML and CSS. Andrew is passionate about writing cross-platform Web apps. He co-curates BrowserExperiments.com and contributes to polyfills and shims to help others. When Andrew is not writing beautiful code, he likes to speak at events between London and Sydney. Andrew currently lives in Sydney and works as a freelance developer.