Matt RaibleMatt Raible is a Web Developer and Java Champion. Connect with him on LinkedIn.

The Angular Mini-Book The Angular Mini-Book is a guide to getting started with Angular. You'll learn how to develop a bare-bones application, test it, and deploy it. Then you'll move on to adding Bootstrap, Angular Material, continuous integration, and authentication.

Spring Boot is a popular framework for building REST APIs. You'll learn how to integrate Angular with Spring Boot and use security best practices like HTTPS and a content security policy.

For book updates, follow @angular_book on Twitter.

The JHipster Mini-Book The JHipster Mini-Book is a guide to getting started with hip technologies today: Angular, Bootstrap, and Spring Boot. All of these frameworks are wrapped up in an easy-to-use project called JHipster.

This book shows you how to build an app with JHipster, and guides you through the plethora of tools, techniques and options you can use. Furthermore, it explains the UI and API building blocks so you understand the underpinnings of your great application.

For book updates, follow @jhipster-book on Twitter.

10+ YEARS


Over 10 years ago, I wrote my first blog post. Since then, I've authored books, had kids, traveled the world, found Trish and blogged about it all.

Implementing OAuth with GWT

I've heard about OAuth for quite some time, but never had an opportunity to implement it on a project. For a good explanation of what OAuth is, see its Introduction. Here's an excerpt:

...it allows you the User to grant access to your private resources on one site (which is called the Service Provider), to another site (called Consumer, not to be confused with you, the User). While OpenID is all about using a single identity to sign into many sites, OAuth is about giving access to your stuff without sharing your identity at all (or its secret parts).

The reason I needed OAuth was to interact with the Google Contacts API. I've always hated how sites make you import all your contacts from Gmail. I wanted to develop a system that'd let you simply read your contacts from Google in real-time.

Since the application I'm working on uses GWT, I chose to implement an OAuth client in GWT. After googling for "gwt oauth", I found two examples. Unfortunately, neither worked out-of-the-box.

The good news is I did manage to create a working solution. The bad news is it only seems to work at random. That's right folks, I created a solution that only works 50% of the time. I'm somewhat embarrassed to post it here, but I also realize the power of open source and community. By sharing, I hope we can find the flaws in my logic and come up with a solution for all GWT applications.

The best project for OAuth libraries seems to be oauth on Google Code. However, you'll notice that there is no JavaScript implementation listed on the homepage. I did look at the Java implementation, but quickly realized it wouldn't be usable in GWT. Therefore, I opted for the JavaScript implementation.

OAuth consists of several steps. The following diagram explains the authentication flow nicely.

OAuth Authentication Flow

In a nutshell, you have to complete the following steps:

  1. Get a token from the service provider.
  2. Redirect user to service provider to grant access and redirect back to application.
  3. Request access token to access protected resources.
  4. Access protected resources and pull/push data.

To access a service provider's OAuth service, you'll likely need to start by registering your application. For Google, OAuth Authentication for Web Applications is an excellent resource. Google's OAuth Playground is a great way to with the Google Data APIs after you've registered.

Now that you know how OAuth works, let's look at how I implemented it with GWT. I started by adding the necessary JavaScript references to my *.gwt.xml file.

<script src="//oauth.googlecode.com/svn/code/javascript/oauth.js"/>
<script src="//oauth.googlecode.com/svn/code/javascript/sha1.js"/>

Next, I needed a way to sign the request. I tried to use Sergi Mansilla's OAuth.java for this, but discovered issues with how the parameters were being written with GWT 1.6. I opted for Paul Donnelly's makeSignedRequest function instead. By adding this to my application's HTML page, I'm able to call it using the following JSNI method:

public native static String signRequest(String key, String secret, String tokenSecret, String url) /*-{
    return $wnd.makeSignedRequest(key, secret, tokenSecret, url);
}-*/;

After the URL is signed, it needs to be sent to the provider to get a request token. To do this, I used GWT's RequestBuilder and created a send() method:

protected void send(RequestCallback cb, String URL) {
    RequestBuilder builder = new RequestBuilder(RequestBuilder.GET, URL);
    builder.setTimeoutMillis(10000);
    builder.setCallback(cb);
    
    Request req = null;
    try {
        req = builder.send();
    } catch (RequestException e) {
        cb.onError(req, e);
    }
}

If you try this with Google's Request Token URL in GWT's hosted mode, nothing will happen. Compile/browse to Safari and you'll still see nothing. Try it in Firefox and you'll see the following.

SOP Error

To workaround browsers' Same Origin Policy, I added a proxy servlet to send the requests. I started with Jason Edwards's ProxyServlet and modified it to fit my needs. I then registered it in both *.gwt.xml and web.xml.

<servlet path="/google/" class="org.appfuse.gwt.servlet.AlternateHostProxyServlet"/>

Now, before calling the send() method, I replace the start of the URL so the request would be routed through the servlet.

public void getToken(RequestCallback cb) {
    String url = signRequest(provider.getConsumerKey(), 
                             provider.getConsumerSecret(), 
                             "", provider.getRequestTokenURL());
    url = url.replace("https://www.google.com/", "/google/");
    send(cb, url);
}

When the request returns, I create two cookies by calling a createOAuthCookies() method with the payload returned:

public static String[] createOAuthCookies(String data) {
    String oauth_token = data.substring(data.indexOf("oauth_token=") + 12);
    oauth_token = oauth_token.substring(0, oauth_token.indexOf("&"));

    String oauth_token_secret = data.substring(data.indexOf("oauth_token_secret=") + 19);

    Cookies.setCookie("oauth_token", URL.decode(oauth_token));
    Cookies.setCookie("oauth_token_secret", URL.decode(oauth_token_secret));
    return new String[]{oauth_token, oauth_token_secret};
}

The next step is to authorize the token. This is where things got tricky with my proxy servlet and I had to add some special logic for GWT. Google was sending back a 302 with a Location header, but it wasn't hitting the onResponseReceived() method in my callback. For this reason, I had to change it to a 200 status code and add the redirect location to the body. I also discovered that sometimes they'd return an HTML page with a <meta http-equiv="refresh" ...> tag. When using Twitter, I discovered the full HTML for the allow/deny page was returned. Below is the callback I'm using. WindowUtils is a class I got from Robert Hanson and the gwt-widget project.

public void onResponseReceived(Request request, Response response) {
    String text = response.getText();
    if (response.getStatusCode() == 200 && response.getText().startsWith("http")) {
        WindowUtils.changeLocation(response.getText());
    } else {
        // look for meta-tag that refreshes and grab its URL
        if (text.contains("";
            String url = text.substring(text.indexOf(tokenToStartWith) + tokenToStartWith.length());
            url = url.substring(0, url.indexOf(tokenToEndWith) + tokenToEndWith.length());
            WindowUtils.changeLocation(url);
        } else {
            // Twitter returns a full HTML page, so redirect to the authorize URL manually
            if (provider instanceof Twitter) {
                String url = provider.getAuthorizeTokenURL();
                url = url.replace("$1", OAuthRequest.getAuthToken());
                url = url.replace("$2", DefaultRequest.getCurrentLocation());
                WindowUtils.changeLocation(url);
            } else {
                onError(request, new RequestException(text));
            }
        }
    }
}

public void onError(Request request, Throwable caught) {
    Window.alert("Calling authorize token failed. " + OAuthPage.STANDARD_ERROR + "\n\n" + caught.getMessage());
}

The 3rd step is to get an access token. The most important thing to remember when you do this is to include the "oauth_token_secret" value when signing the request.

signRequest(provider.getConsumerKey(), provider.getConsumerSecret(), 
            getAuthTokenSecret(), url);

After this completes with a 200, I create the cookies again (since oauth_token and oauth_token_secret are returned in the body), then call the API to get a list of contacts. The ContactsRequests class is responsible for making the call. The DefaultRequest class contains the send() method as well as utility methods to get the cookie values of the oauth tokens.

public class ContactsRequest extends DefaultRequest {
    private static final String GOOGLE_CONTACTS_URL = 
        "http://www.google.com/m8/feeds/contacts/default/thin?oauth_token=$1";
    private OAuthProvider provider;

    public ContactsRequest(OAuthProvider provider) {
        this.provider = provider;
    }

    public void getContacts(RequestCallback cb) {
        String url = GOOGLE_CONTACTS_URL.replace("$1", getAuthToken());
        url = signRequest(provider.getConsumerKey(), provider.getConsumerSecret(), 
                          getAuthTokenSecret(), url);

        String proxiedURLPrefix = "/contacts/";
        // allow for deploying at /gwt-oauth context
        if (WindowUtils.getLocation().getPath().contains("gwt-oauth")) {
            proxiedURLPrefix = "/gwt-oauth" + proxiedURLPrefix;
        }

        url = url.replace("http://www.google.com/", proxiedURLPrefix);

        send(cb, url);
    }
}

If all goes well, the response contains the data you requested and it's used to populate a textarea (at least in this demo application). Of course, additional processing needs to occur to parse/format this data into something useful.

This all sounds pretty useful for GWT applications, right? I believe it does - but only if it works consistently. I sent a message to the OAuth Google Group explaining the issues I've had.

I'm trying to use the JavaScript API to authenticate with OAuth from a GWT application. I've got it working with both Google and Twitter's OAuth implementations. However, it seems to fail to sign the URL at random. In other words, it works 1 out of 3 times. ... Any idea why this could be happening?

I received a response with a cleaner makeSignedRequest() function. I tried it and, unfortunately, it seems to be equally unreliable. I suspect the problem is with the OAuth JavaScript implementation, GWT's interpretation of it, or that OAuth isn't as mature as it appears to be. I'd like to think one of the first two causes the problem.

To make it easier to create a robust example of GWT and OAuth, I created a gwt-oauth project you can download or view online. Please keep in mind the demo is likely to be flakey. If you're persistent and try enough times, it's likely to work. Firefox seems to succeed moreso than Safari or Chrome. If you have any suggestions for improving this example, please let me know.

Posted in Java at Jun 18 2009, 01:59:13 PM MDT 13 Comments
Comments:

We should just build this as native GWT library. I'm wary of using JS libraries for critical functions like that. OAuth not terribly complex, if there is Java source code examples it's not hard to refactor them to be GWT translatable.

I already have working (unit tested) SHA1 and HmacSHA1 code to contribute.

It would also be nice if this library was abstracted enough so it can be used with RequestBuilder or with the (upcoming) RPC enhancements which will let you plug in a handler to sign requests before they are sent.

I'll check out the project and see what I can do.

Posted by Mark Renouf on June 22, 2009 at 08:36 AM MDT #

You might want to make your proxy servlet compute the oauth_signatures. That could improve security, by not exposing your consumer secret to browsers. And you could implement it in Java, instead of JavaScript.

Posted by John Kristian on June 23, 2009 at 04:39 AM MDT #

Thinking some of your signature inconsistency problems might go away if you replace:

paramList.push("oauth_signature=" + theSig);

with...

paramList.push("oauth_signature=" + encodeURIComponent(theSig));

Posted by BN on October 02, 2009 at 09:14 PM MDT #

@BN - Thanks for the suggestion, but unfortunately it doesn't help with Google. I just tried it 5 times with your change and 5 times w/o. It didn't work on any attempt. Works every time with Twitter though.

Posted by Matt Raible on October 07, 2009 at 03:27 PM MDT #

Hi Mark, I would be very interested in getting your HMAC-SHA1 algorithm into the gwt-crypto project ( http://code.google.com/p/gwt-crypto/ ), which I help maintain. I have had people emailing me looking for an implementation. Please let me know if that would be possible. Thanks, Dan

Posted by Dan Moore on November 01, 2009 at 08:31 PM MST #

For future google searches: Check out gwt-gdata and the demo

Posted by S on November 03, 2009 at 02:26 PM MST #

@Dan - are you referring to the first commenter (Mark) or me (Matt)? In case it's me, I'm fine with you using this code as long as credit is given (a link to this blog post or the demo is fine). However, you also might check out the last commenter's links to gwt-gdata and its demo.

Posted by Matt Raible on November 03, 2009 at 10:40 PM MST #

@Matt,

Thanks! I was actually interested in Mark's native GWT implementation of HMAC SHA-1 that he references in his comments. I want to integrate it into the gwt-crypto project if he's amenable. (Sorry if I wasn't clear.) I would have sent him an email or contacted him via his website, if he had left that. I just found someone who seems like they might be that Mark--I'll send an email to him directly.

Perhaps I missed something but I thought your implementation was dependent on javascript libraries?

I took a look at the projects that 'S' posted, but it appears that they also leverage javascript calls to actually do the authentication (and I presume encryption). Here's a quote from com.google.gwt.accounts.client.User:

"Visit the following documentation to learn more about authenticating with AuthSubJS: http://code.google.com/apis/gdata/client-js.html#Authenticating"

Posted by Dan Moore on November 03, 2009 at 11:28 PM MST #

[Trackback] A reader recently asked : I would love to see a snippet of how to eval the JSON coming from RequestBuilder into the OverlayTypes. What is the mapping like? I used OverlayTypes to read in static data that I render into the head section of the host...

Posted by Raible Designs on November 24, 2009 at 10:48 PM MST #

[Trackback] When I worked at LinkedIn last year, I received a lot of inquiries from friends and developers about LinkedIn's APIs. After a while, I started sending the following canned response: For API access to build LinkedIn features into your application, ...

Posted by Raible Designs on November 24, 2009 at 10:48 PM MST #

My GWT OAuth GData Demo and Source: http://code.google.com/p/gwt-examples/wiki/DemoGwtGData

Posted by Brandon Donnelson on January 15, 2011 at 03:11 AM MST #

@othman - the ProxyServlet is used to get around the cross-domain issue in web browsers. If you run "mvn jetty:run" with the example, it should work just fine. If Eclipse doesn't work for you, I'd try IntelliJ IDEA - it has much better Maven support.

Posted by Matt Raible on April 12, 2011 at 03:43 PM MDT #

hi- i'm trying to run your code from my eclipse IDE with GWT plugin.

what is the ProxyServlet use in this code? and why in some portions you replace some url portion with another url that you called proxiedURL?

why not use direct url and use a proxied url? when i run the example it says file not found for URI: /google/accounts/GetRequestToken etc.. can you tell from where comes this error message?

thanks

Posted by othman on April 22, 2011 at 02:12 PM MDT #

Post a Comment:
  • HTML Syntax: Allowed