Yesterday, I played around with authenticating a user with my Google Webtoolkit application hosted on Google App Engine against Twitter. I have no idea what I am going to do with it, but as most Twitter authentication HOWTOs on the web either deal with GWT or with GAE, I summerized the steps here. I will provide the source code of the project once this blog post is online.

Create your GWT/GAE project

I assume that you are going to use Eclipse and the Google plugin. Create a new GWT/GAE project with your favorite settings and clean up the pre-generated classes.

Bildschirmfoto 2010-08-20 um 19.11.56

Just delete the GreetingService*-classes and the FieldVerifier, we don't need them.

Bildschirmfoto 2010-08-20 um 19.12.59

Also, delete the servlet from the web.xml file and clean up the generated main class.

Bildschirmfoto 2010-08-20 um 19.14.18

Download twitter4j

First, download the twitter4j library. There are alternatives, but for me this library worked well. I used twitter4j-2.1.4-SNAPSHOT.zip but you may want to check if there are newer versions. Unzip the archive and copy the file twitter4j-core-2.1.4-SNAPSHOT.jar to your /war/WEB-INF/lib folder. Add the jar to your project's classpath.

Create your application on Google App Engine

We need to register your domain to the Twitter API in a quite early stage, so you should create your Google App Engine application right now. You need a Google account to do so. Login to https://appengine.google.com and select "Create an Application".

Bildschirmfoto 2010-08-20 um 19.20.20

Now enter the Application ID that you have chosen into your project's properties in Eclipse:

Bildschirmfoto 2010-08-20 um 19.21.59

From now on, you can deploy your GAE application. The default URL is composed by your Application ID and Google's host appspot.com. In my example, this is http://gwtgaeauthtwitter.appspot.com

Register your application at Twitter

Open http://dev.twitter.com/apps/new and login to Twitter, if you are not already logged in. Fill out the form:

Bildschirmfoto 2010-08-20 um 19.27.24

Most settings should be self-explainatory. The most important one for the authentification step is the call-back URL. Many servlet based tutorials enter here the mapped URL of a specific servlet, but as we are going to create a ass kicking Ajax GWT application, just enter your host here. We will take care about delegating to the server in the entry point of our GWT application later. [Update: you have to enter the URL pointing to your host/domain when you go online with your service; during development, simply enter your complete http://127.0.0.1:8888/... URL into the callback field; then, Twitter automatically redirects to your local server where you can debug your application.]

Complete the form and make sure you remember two codes you will receive on the confirmation screen: the Consumer Key and the Consumer Secret. We will need them in the next steps.

Create the main authentication servlet

The idea behind the authentication of the GWT is to check on first load of the application in the browser by a server call if the user is currently logged in by the current session. If logged in, we return some information that can be used by the client application to continue. If not logged in, we return a login link. Actually, this is the identical principle as logging in (or checking the login) using Google's GAE login services.

The authentication servlet looks like this:

package ggat.server;

import java.util.logging.Logger;
import ggat.client.GCredentials;
import ggat.shared.ITwitterServlet;
import javax.servlet.http.HttpSession;
import twitter4j.Twitter;
import twitter4j.TwitterException;
import twitter4j.TwitterFactory;
import twitter4j.User;
import twitter4j.http.AccessToken;
import twitter4j.http.RequestToken;
import com.google.gwt.user.server.rpc.RemoteServiceServlet;

public class TwitterServlet extends RemoteServiceServlet implements ITwitterServlet {

private static final long serialVersionUID = 3681517323829124979L;
private static final String CONSUMER_KEY = <your consumer key as string>;
private static final String CONSUMER_SECRET =
<your consumer secret key as string>;

@Override
public GCredentials login() {
Twitter twitter = new TwitterFactory().getInstance();
twitter.setOAuthConsumer(CONSUMER_KEY, CONSUMER_SECRET);

HttpSession session = super.getThreadLocalRequest().getSession();

User user = getUserLogin( twitter, session );

if (user != null) {
GCredentials result = new GCredentials();
result.setUser( user.getName() );
return result;
} else {
return requestToken( twitter, session );
}
}

@SuppressWarnings("deprecation")
protected User getUserLogin( Twitter twitter, HttpSession session ) {
// try to get credentials
User user = null;
if ((session.getAttribute("token") != null) && (session.getAttribute("tokenSecret") != null)) {

try {
AccessToken accessToken = twitter.getOAuthAccessToken(
(String) session.getAttribute("token"),
(String) session.getAttribute("tokenSecret"));
twitter.setOAuthAccessToken(accessToken);

user = twitter.verifyCredentials();
} catch (TwitterException e) {
if (e.getStatusCode() == 401) {
Logger.getLogger( GCredentials.class.getName() ).info( e.getMessage() );   
} else {
Logger.getLogger( GCredentials.class.getName() ).severe( e.getMessage() );
}
}
}
return user;
}

private GCredentials requestToken( Twitter twitter, HttpSession session ) {   
RequestToken requestToken;
try {
session.setAttribute("token", null);
session.setAttribute("tokenSecret", null);
requestToken = twitter.getOAuthRequestToken();
} catch (TwitterException e) {
Logger.getLogger( GCredentials.class.getName() ).severe( e.getMessage() );
return null;
}

String token = requestToken.getToken();
String tokenSecret = requestToken.getTokenSecret();

session.setAttribute("token", token);
session.setAttribute("tokenSecret", tokenSecret);

GCredentials result = new GCredentials();
result.setLoginURL( requestToken.getAuthorizationURL() );
return result;
}
}

The servlet provides one public method which is also defined in the client/server interface classes and which is later called from the client during onLoad(). This method login() returns a plain POJO to the client that contains either a user name as String as indication that the user is logged in or an URL as String which is to be used to login through Twitter if the user is not logged in.

This method first calls an internal, protected method getUserLogin() that tries to verify the current session credentials (and currently makes use of depricated methods which, however, currently work). If this fails, it returns null. This method can be later as starting point for an authentication methods used by other servlets to check if the user is logged in in the current session or to retrieve the Twitter client object. This pattern is similar to using Google's GAE authentication methods as starting point for servlet call authentications for methods that expect a valid login. [Edit: make sure that you do not evoke this function more than once per session, as it counts on the rate limit of  Twitter API calls. Best is to check the valid login at the beginning of the session and then to use the OAuth token for subsequent calls without checking the validity. If they are expired for some reasons, you will get corresponding error codes anyway.]

The third, private method requestToken() is used only to retrieve a login URL in the case that the user is not logged in. Make sure you remove the token and tokenSecret attribute before requesting getOAuthRequestToken() as you may get a 401:Authentication credentials were missing or incorrect. Failed to validate oauth signature and token else.

To make the servlet usable, create the interfaces:

package ggat.shared;
import ggat.client.GCredentials;
import com.google.gwt.user.client.rpc.RemoteService;
/*
*             gmodel                                 ~        controller
*                                                    ~
*  +----------------------+     +----------------+   ~    +----------------------+
*  |  ServiceDefTarget    |     | RemoteService  |   ~    | RemoteServiceServlet |
*  +----------------------+     +----------------+   ~    +----------------------+
*                                     ^           ~               ^
*  +----------------------+     +----------------+   ~    +-----------------------+
*  | ITwitterServletAsync |     |ITwitterServlet |<--~----|      TwitterServlet   |
*  +----------------------+     +----------------+   ~    +-----------------------+
*
*/

/
* The client side stub for the RPC service.
*/

public interface ITwitterServlet extends RemoteService {
/
read user information or login URL from server */

public GCredentials login();
}

And the corresponding client interface:

package ggat.shared;
import ggat.client.GCredentials;
import com.google.gwt.user.client.rpc.AsyncCallback;
/
* The async counterpart of <code>GreetingService</code>.
*/

public interface ITwitterServletAsync {
/
read user information or login URL from server */

void login(AsyncCallback<GCredentials> callback);
}

Don't forget to update your web.xml file:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE web-app
PUBLIC "-Sun Microsystems, Inc.DTD Web Application 2.3EN"
"http:
java.sun.com/dtd/web-app_2_3.dtd">
<web-app>
<!-- Servlets -->
<servlet>
<servlet-name>twitterServlet</servlet-name>
<servlet-class>ggat.server.TwitterServlet</servlet-class>
</servlet>

<servlet-mapping>
<servlet-name>twitterServlet</servlet-name>
<url-pattern>/gwtgaeauthtwitter/twitter</url-pattern>
</servlet-mapping>

<!-- Default page to serve -->
<welcome-file-list>
<welcome-file>Gwtgaeauthtwitter.html</welcome-file>
</welcome-file-list>
</web-app>

Also update your appengine-web.xml file and add session support:

<sessions-enabled>true</sessions-enabled>

Create the client application

For starting, I've added just a link and a label to the client application. I've simply replaced the generated headline and table by this:

<h1>GWT GAE Authentication with Twitter</h1>
<center>
<div id="buttonLogin"></div>
<div id="labelResult"></div>
</center>

The client class consist of some UI code and the server call to check the login state or to retrieve the login URL:

Bildschirmfoto 2010-08-21 um 12.44.45

The entry method onModuleLoad() simply adds a label and an anchor (a hyperlink) to the main page. The idea of the startup procedure is the following:

  1. Add a status label and an anchor
  2. Hide the anchor and ask the user to wait for authentication
  3. If we get a user name back, swith to "login mode" (here: by welcoming the user)
  4. If we get a login URL back, set the URL as target for the anchor and show the anchor

That's it. Deploy your application to the GAE and open your favorite
browser.

Result

Unlike implementations which make use of different servlets and client URLs for retrieving the login or calling back the application, we just use the plain URL of our app. When the user evokes the application the first time, the page shows the login link to Twitter.

Bildschirmfoto 2010-08-21 um 13.01.23

By clicking on the Twitter link, the well known "allow access screen" shows up. The following screenshot is taken from a browser where I already logged in to Twitter before (else, a login promt will appear):

Bildschirmfoto 2010-08-21 um 13.01.54

By selecting "Allow", Twitter calls back our application. Remember, you entered a call back URL before while creating your Twitter Application Account. This URL now evokes our application a second time; also, the onModuleLoad() is executed a second time (as the page is loaded from scratch).

Bildschirmfoto 2010-08-21 um 13.02.23

Next steps

Voilá. This now could be your starting point to retrieve more information and to interact with the user's Twitter account. You basically don't need to pass any additional login information as payload of your calls (as long as you don't want to implement further security measures), just evoke the getUserLoginU() method within your additional servlets and functions. [Edit: as mentioned before, authenticating is rate limited, so be sure to reduce this call to a minimum per session.]

I provide you as starting point the zipped project here. Also, I will investigate how to get rid of the deprecated methods. If anybody out there already knows, please leave me hints or a link. Whoever has an idea how I can publish formatted code with blog.de please also leave me a hint. Thanks!

Pages that helped me: