Implementing OAuth Authorization on Social Networks
by Jaswinder Singh
March 9, 2010
Walk through all the necessary steps for implementing the OAuth token-based authorization systema perfect security solution for the social networking ageon both the consumer and provider sides.
Web users today have their social data (profiles, friends, photos, videos, messages, and so on) scattered across different social sites and they want to access and use this data from outside these sites. This leaves developers facing a serious security challenge: how to enable users to access their private data in social sites without having to share their credentials. OAuth is the perfect solution. This open authorization protocol allows standard and secure API authorization without exposing the user's credentials. OAuth also provides a mechanism to grant limited access (in scope, duration, and so on).
At a high level, the elements involved in the OAuth token-based authorization system are:
User: Social network (Orkut, Facebook, Twitter, iGoogle, etc.) users like you and me
OAuth Provider: Web site or social networking site where the user's private resources are stored
OAuth Consumer: Web site, social networking site, mobile device, set-top box, etc. trying to access the protected resource on the other site
In this article, I will take you through the necessary steps for implementing OAuth on both the consumer and provider sides.
Implementing OAuth
To understand how you implement OAuth on the consumer and provider sides, take the example of an OAuth Consumer (Google OAuth gadget) that is trying to access the protected resources stored at an OAuth Provider. Google OAuth gadget is a simple XML file deployed on the iGoogle container, and the container will take care of all the OAuth requests made by the gadget. (For more information on how to create a Google OAuth gadget, read "Writing OAuth Gadgets.")
Here are the relevant files for this example:
My_Consumer (OAuth Consumer): A sample XML file deployed on iGoogle container
My_Provider (OAuth Provider): A sample Java project deployed on some server
My Consumer
To Implement OAuth, the Consumer gadget needs to have the following things:
OAuth Provider name
End point (Provider URL that User wants to access)
<OAuth></OAuth> section inside <ModulePrefs>
A method to link user to OAuth Provider authorization page if user has not authorized the request (OAuth Provider will then take User through the generation and approval of access token.)
A call to the gadgets.*.makeRequest ( ) method with OAuth Parameters.
Logic to process and display the response data
Here is the code for the My_Consumer Gadget XML file:
<?xml version="1.0" encoding="UTF-8" ?>
<Module>
<ModulePrefs title="My Gadget(OAuth Consumer)" scrolling="true">
<Require feature="opensocial-0.8" />
<OAuth>
........
..........
</OAuth>
</ModulePrefs>
<Content type="html">
<![CDATA[
..........
........
// Invoke makeRequest() to fetch data from the service provider endpoint.
// If the user hasn't approved access yet, response.oauthApprovalUrl contains a
// URL that includes a request token. This is presented in the
// gadget as a link that the user clicks to begin the approval process.
function fetchData() {
var params = {};
url = "http://my_provider.myComp.com/myProtectedPage?alt=text";
.........
.........
gadgets.io.makeRequest(url, function (response) {
if (response.oauthApprovalUrl) {
.................
...............
}, params);
}
// Call fetchData() when gadget loads.
gadgets.util.registerOnLoadHandler(fetchData);
</script>
]]>
</Content>
</Module>
This is the sequence of events the My_Consumer gadget follows to access data from OAuth Provider:
At the time of loading, the gadget calls the gadgets.util.registerOnLoadHandler(fetchData); method, which invokes the fetchData() function.
FetchData() calls makeRequest().
Because makeRequest() includes the callback parameter, when the callback is invoked, it is passed with some OAuth-related parameters and the values returned by the makeRequest() function.
Here is the Entry method for the gadget:
function fetchData() {
var params = {};
url = "http://my_provider.myComp.com/myProtectedPage?alt=text";
params[gadgets.io.RequestParameters.CONTENT_TYPE] = gadgets.io.ContentType.TEXT;
params[gadgets.io.RequestParameters.AUTHORIZATION] = gadgets.io.AuthorizationType.OAUTH;
params[gadgets.io.RequestParameters.OAUTH_SERVICE_NAME] = "MyService";
params[gadgets.io.RequestParameters.OAUTH_USE_TOKEN] = "always";
params[gadgets.io.RequestParameters.METHOD] = gadgets.io.MethodType.GET;
gadgets.io.makeRequest(url, function (response) {
if (response.oauthApprovalUrl) {
// Create the popup handler. The onOpen function is called when the user
// opens the popup window. The onClose function is called when the popup
// window is closed.
var popup = shindig.oauth.popup({
destination: response.oauthApprovalUrl,
windowOptions: null,
onOpen: function() { showOneSection('waitingForProviderAuthSection'); },
onClose: function() {fetchData();}
});
var personalize = document.getElementById('personalize');
personalize.onclick = popup.createOpenerOnClick();
showOneSection('personalize');
}
else if (response.data) {
showOneSection('main');
showResults(response.data);
}
else {
............
.........
}
}, params);
}
function showOneSection(toshow) {
........
......
}
<div id="personalize" style="display: none">
<img src="........">
<a href="#" id="personalize">Click here to make OAuth Call to Provider</a>
</div>
Some additional parameters are required to tell the container about the authorization type, service to use, etc. Table 1 lists some of the important parameters.
Table 1: Important Parameter Description
Parameter Name
Description
URL
Service Provider URL, which the user wants to access.
gadgets.io.RequestParameters.AUTHORIZATION
Type of Authorization; if set to OAUTH, then container needs to use OAuth to access the protected resource specified in the request.
gadgets.io.RequestParameters.OAUTH_SERVICE_NAME
Name used to access the service defined in the <OAuth> section (In this case, "MyService")
Name of the OAuth Service (In this case, "MyService" to be called by fetchData() method)
Request URL
Service provider URL to be called to get request token
Authorization URL
Service provider URL to authorize the request token
Access URL
Service provider URL to be called to get access token in exchange for request token
My Provider
In this example, I have deployed My_Provider (OAuth Provider) on the myComp (My Company) server, which User or My_Consumer (OAuth Consumer) can access through the URL http://my_provider.myComp.com.
To implement OAuth, Provider needs the following three things:
URLs (Access, Request, Authorization) that Consumer needs while making OAuth requests and HTTP methods (GET, POST, etc.)
Validator to validate the requests signature methods, which are needed for signing the requests
Other additional methods or parameters required by the service provider to get tokens
My_Provider mainly contains the files listed in Table 3.
Table 3: Provider Files
Files
Description
ProvideRequestToken.java
Servlet to get the Request Token
ProvideAccessToken.java
Servlet to get the Access Token
AuthorizeRequestToken.java
Servlet to authorize the Request Token
OAuthConsumer.properties
Properties file to store consumer key and secret
Web.xml
To map the URLs with the respective Servlets
The Authentication Process
The following are the steps involved in the authentication process.
First Step: Registration Between Consumer and Service Provider
As you are using your own service provider, Consumer does not need to send any requests to Provider for registration. Instead, Service Provider can share a unique key and secret with Consumer. Service Provider and Consumer both can store the key and secret on their ends. In this example, you can take some default values for the key and secret to be stored at both the ends.
Consumer Key: My_Key
Consumer Secret: My_Secret
Gadgets deployed on the iGoogle container are publicly accessible, so they are not good places to store keys and secrets. Instead, you can store them in the containers on which they are deployed. In this example, however, My_Consumer can store its key and secret on iGoogle by sending a message to oauthproxyreg@google.com with the information listed in Table 4.
Table 4 : Registration Details
Parameter Name
Description
Gadget URL
The URL where you deploy your gadget
OAuth Consumer Key
Shared by the Provider during registration
OAuth Consumer Secret
Shared by the Provider during registration
OAuth Service Name
Name of the service gadget will be using for authorization
My_Provider can store the key and secret in the OAuthConsumer.properties file.
Second Step: Consumer Requests for Request Token
The first time when the My_Consumer gadget gets loaded, it will call the fetchData() method, which will internally invoke the gadget.io.makeRequest() function. This function will make requests for the protected resource stored in the URL (Service Provider URL that User wants to access). Because the URL is OAuth protected, it first will make a request for a request token with the Provider (My_Provider in this case). At that point, the gadget will check for the mapped OAuth Service name (MyService in this case) and then the RequestURL defined in the <OAuth> section of that service, which is mapped to the ProvideRequestToken.java Servlet of the Provider (see code below), will be called.
ProviderRequestToken servlet:
public class ProvideRequestToken extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
....
addConsumers(config);
....
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
..............
handleRequest(request, response);
}
public void doPost{}
public void handleRequest(HttpServletRequest request, HttpServletResponse response)
{
....
OAuthMessage oauthRequestMessage = OAuthServlet.getMessage(request, null);
// get the consumer from the provider if previously registere
OAuthConsumer oauthConsumer = getOAuthConsumer(oauthRequestMessage);
OAuthAccessor oauthAccessor = new OAuthAccessor(oauthConsumer);
// validate request message
validateRequestMessage(oauthRequestMessage,oauthAccessor);
............
// generate request_token and secret and set it to the accessor object
generateRequestToken(oauthAccessor);
response.setContentType("text/plain");
OutputStream outputStream = response.getOutputStream();
// set the request token and secret to OAuth object and send it back to the consumer
OAuth.formEncode(OAuth.newList("oauth_token",oauthAccessor.requestToken,
"oauth_token_secret", oauthAccessor.tokenSecret),outputStream);
outputStream.close();
}
}
OAuthConsumer.properties file:
#Entries of all the consumer registered with the provider.
#First consumer
ConsumerKey=My_Key
ConsumerSecret=My_Secret
ConsumerKey.description=My_Consumer Gadget.
#Second consumer
ConsumerKey=.....
ConsumerSecret=......
ConsumerKey.description=........
Here is the workflow of the ProviderRequestToken Servlet:
As an entry point to the Servlet, first init ( ) gets called, which will internally call the addConsumers(config) method.
Here is the authorization Servlet to authorize the generated request token:
public class AuthorizeRequestToken extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException {
OAuthMessage oauthRequestMessage = OAuthServlet.getMessage(request, null);
OAuthAccessor oauthAccessor = getAccessor(oauthRequestMessage);
if (Boolean.TRUE.equals(oauthAccessor.getProperty("authorized"))) {
// if already authorized send the user back to consumer
redirectBackToConsumer(request, response, oauthAccessor );
}
else {
// redirects the user to authorize page to authorize
// the request token by clicking of authorize button.
redirectToAuthorizePage(request, response, oauthAccessor);
}
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response){
...........
...........
}
private void redirectToAuthorizePage(HttpServletRequest request,
HttpServletResponse response, OAuthAccessor oauthAccessor)
throws IOException, ServletException{
..................
......................
//set the request attributes along with the request token to authorize the use
request.setAttribute("CONSUMER_DESCRIPTION", consumer_description);
request.setAttribute("CALLBACK_URL", oauthCallbackURL);
request.setAttribute("TOKEN", oauthAccessor.requestToken);
request.getRequestDispatcher("/authorizeRequestToken.jsp").forward(request,response);
}
Provider will put the entire consumer defined in the properties file (Refer fig.8) into a hashmap (let's say OAUTH_CONSUMERS) as a (key, value) pair (Refer fig.9).
Here is the Accessor method that Servlet uses to get the accessor:
/**
* Get the accessor for the given oauth_token.
*/
public static synchronized OAuthAccessor getAccessor(OAuthMessage requestMessage)
throws IOException, OAuthProblemException {
// try to load from local cache if not throw exception
String consumer_token = requestMessage.getToken();
OAuthAccessor oAuthAccessor = null;
for (OAuthAccessor accessor :OAUTH_TOKENS) {
if(accessor.requestToken != null) {
if (accessor.requestToken.equals(consumer_token)) {
oAuthAccessor = accessor;
break;
}
} else if(accessor.accessToken != null){
if (accessor.accessToken.equals(consumer_token)) {
oAuthAccessor = accessor;
break;
}
}
}
if(oAuthAccessor== null){
OAuthProblemException problem = new OAuthProblemException("invalid_token");
throw problem;
}
return oAuthAccessor;
}
Because the OAuth section of the gadget defines the Request URL method to be GET, the control will move to the GET method of the Servlet, which will invoke the handleRequest(....) method.
Here is the summary of the other methods used in the ProvideRequestToken Servlet:
/**
* Load Consumers from the properties file
*/
public static synchronized void addConsumers(ServletConfig config){
Properties p = oauthConsumerProperties;
......
.............
//Create new OAuthConsumer by loading consumer properties from properties file
OAuthConsumer oauthConsumer = new OAuthConsumer(consumer_callback_url,consumer_key,consumer_secret,null);
......
OAUTH_CONSUMERS.put(consumer_key,oauthConsumer);
}
/**
* Get the consumer if registered with the provider else throw exception.
*/
public static synchronized OAuthConsumer getOAuthConsumer(OAuthMessage oauthRequestMessage)
throws IOException{
OAuthConsumer oauthConsumer = null;
String consumer_key = oauthRequestMessage.getConsumerKey();
oauthConsumer = OAUTH_CONSUMERS.get(consumer_key);
........
return oauthConsumer;
}
/**
* Generate a fresh request token and secret for a consumer.
*/
public static synchronized void generateRequestToken(
OAuthAccessor oauthAccessor)
String requestToken;
String secret;
.............
......................
oauthAccessor.requestToken = requestToken;
oauthAccessor.tokenSecret = secret;
oauthAccessor.accessToken = null;
OAUTH_TOKENS.add(oauthAccessor);
}
}
The request made by the gadget is first parsed and converted to an OAuth-type request message (used in the OAuth protocol) by calling getMessage ( ) on OAuthservlet. It will parse the request and put all the parameters in a hashmap as a (key, value) pair.
The OAuth message object oauthRequestMessage includes all the parameters in the request (Gadget request), along with some OAuth-related parameters.
Moving ahead, Provider will try to search for the requested consumer in the OAUTH_CONSUMERS hashmap by using the consumerKey passed with the request. When the provider gets the consumer, it will wrap the consumer along with other user-specific parameters (requestToken, accessToken, tokenSecret) into an OAuthAccessor object. Presently, all these parameters, which the provider will set later on, will be initialized to null.
Here are the methods used by the authorization Servlet:
public void doPost(HttpServletRequest request, HttpServletResponse response{
OAuthMessage oauthRequestMessage= OAuthServlet.getMessage(request, null);
OAuthAccessor oauthAccessor = getAccessor(oauthRequestMessage);
//Checks for the user name and password set by the user .
String userId = request.getParameter("userId");
String password = request.getParameter("password");
.................
// set userId and password in accessor and mark it as authorized
markRequestTokenAsAuthorized(oauthAccessor,userId,password);
// return the control back to the consumer to get the access token.
redirectBackToConsumer(request, response,oauthAccessor );
}
private void redirectBackToConsumer(HttpServletRequest request,
HttpServletResponse response, OAuthAccessor accessor)
throws IOException, ServletException{
// send the user back to site's callBackUrl
String callback = request.getParameter("oauth_callback");
........
.......
String token = accessor.requestToken;
if (token != null) {
callback = OAuth.addParameters(callback,"oauth_token",token);
}
response.setStatus(HttpServletResponse.SC_MOVED_TEMPORARILY);
response.setHeader("Location", callback);
}
}
public static synchronized void markRequestTokenAsAuthorized(OAuthAccessor oauthAccessor,
String userId,String password)throws OAuthException {
.............
..............
//set the accessor properties to authorize the respective user.
oauthAccessor.setProperty("user", userId);
oauthAccessor.setProperty("password", password);
oauthAccessor.setProperty("authorized", Boolean.TRUE);
// update token in local cache
OAUTH_TOKENS.add(oauthAccessor);
}
}
}
Before generating the request token, the provider will first validate the signature methods of the request by calling validateRequestMessage (…).
When the request is validated, the provider will generate the request token and set the respective variables to the accessor. The equest token can be a unique string, generated by using any algorithm.
After all is done, provider will encode and write all data to the response output stream (response object) and send it back to the gadget.
Third Step: Authorization of the Request Token
The following is the OAuth Consumer flow to call the AuthorizeRequestToken Servlet:
The output from the provider and some additional parameters will be passed to the callback method of the Consumer gadget.
At the start, response.oauthApprovalUrl will contain the URL that the user needs to visit to give the gadget access for the protected data. Part of the URL will contain the request token obtained from the provider. So, the first time, the gadget will go into an if loop of the callback method and internally call the showOneSection (…) method.
This method will show the section based on the parameter passed. Here, it will show the Personalize section of the gadget, which display a link titled
Click here to make OAuth call to Provider.
Because the Personalize section is linked with shindin.oauth.popup, when the user clicks the link a pop-up section of the callback method will be invoked and the gadget will make a request to the authorization URL of the provider.
The following is the provider workflow to authorize the request token:
As the OAuth section of the gadget defines the authorization URL method to be GET, so control will move to the GET method of the Servlet, which will first look for the accessor by calling the getAccessor (..) method. If the request message contains the same request token as that of the provider, the getAccessor method will return the OAuthAccessor object. If not, it will throw an invalid token error.
Now the Servlet will check for the accessor authorized property. If it was previously authorized, then the Servlet returns back to the consumer. If not, it sends the user to the authorization page.
This will forward the user to the authorization page. This page can be a simple JSP page displaying a link for the user to authorize the gadget. If the user has not previously logged in to the service provider (which is the case here), he or she will be redirected to the login page and after log-in back to the authorization page.
When the user authorizes the gadget, the user will be redirected from the authorization page to the Servlet and this time to the doPost method because the request made by the authorizeRequestToken.jsp page is POST.
In this method, the Servlet will authorize the request token by calling the authorizeRequestToken (……..) method. The user credentials (UserId and Password to the provider) are passed as a parameter, along with the accessor. This method sets the userId, password, and authorized property (boolean property marked as true) in the accessor, which represents that the user has authorized the request token.
When the request token is authorized, control returns to the redirectBackToConsumer ( ) method, where it will check for the callback parameter if any is set by the gadget. Otherwise, it will look into the properties file for the same.
Here's the code to generate the access token:
/**
* Generate a fresh access token for a consumer.
*
* @throws OAuthException
*/
public static synchronized void generateAccessToken(OAuthAccessor oauthAccessor)
throws OAuthException {
String accessToken;
// Code to generate the access token
..............
.............
// first remove the accessor from cache
OAUTH_TOKENS.remove(oauthAccessor);
oauthAccessor.requestToken = null;
oauthAccessor.accessToken = accessToken;
// update token
OAUTH_TOKENS.add(oauthAccessor);
}
In the <OAuth> section, you passed the oauth_callback parameter as a part of the request and then passed to the authorizeRequestToken.jsp. Hence, here you will get the callback URL.
When the request is completed, the callback URL will be called and control will be send back to the gadget.
Fourth Step: Exchanging the Request Token with the Access Token
When the user has authorized the request token, the gadget makes a call to the access URL to exchange the signed request token with the access token.
Here's the code to provide an access token in return for a request token:
public class ProvideAccessToken extends HttpServlet {
public void init(ServletConfig config) throws ServletException {
super.init(config);
}
public void doGet(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {
handleRequest(request, response);
}
public void doPost(HttpServletRequest request, HttpServletResponse response){
................
......
}
public void handleRequest(HttpServletRequest request, HttpServletResponse response)throws IOException, ServletException {
OAuthMessage oauthRequestMessage = OAuthServlet.getMessage(request, null);
OAuthAccessor oauthAccessor = SampleOAuthProvider.getAccessor(requestMessage);
validateMessage(requestMessage, accessor);
// make sure token is authorized
if (!accessor.getProperty("authorized")) {
OAuthProblemException problem = new OAuthProblemException("permission_denied");
throw problem;
}
// generate access token and secret
generateAccessToken(oauthAccessor);
response.setContentType("text/plain");
OutputStream outputStream = response.getOutputStream();
OAuth.formEncode(OAuth.newList("oauth_token",oauthAccessor.accessToken,
"oauth_token_secret",oauthAccessor.tokenSecret),outputStream);
outputStream.close();
}
}
Here is the provider workflow to generate the access token:
When the provider receives the request for the access token, it first checks the authorization of the request token by checking the authorized property of the accessor.
If the request token is authorized, it calls for the generation of an access token and sends it back to the consumer. While setting the generated token to the accessor, it makes the request token to null, as the request token is used only once to get the access token.
Now with this access token, My_Consumer gadget can easily access the URL (Provider URL where protected data is stored) and give it to the user without having its credentials shared.
Conclusion
OAuth has provided an easy and secure way to provide third-party access to users of web resources without their having to share their passwords. To date, OAuth 1.0a is the most successful protocol deployed on the web. Some argue that OAuth is too hard to implement, but as you can conclude from this article, it's not a big job to implement OAuth. So start implementing it and make your web application more secure.
About the Author
Jaswinder Singh works as a Technology Analyst at SETLabs (the R&D wing of Infosys Technologies Ltd). He has experience in the developing Java and Java EE applications. Some of the key technologies he has worked with are Google Gadgets, Google App Engine, Software Factory, Eclipse Plugin Architecture, and Cloud Computing.