Using Jersey Client OAuth Support with SmugMug

August 10th, 2009 by Martin Leave a reply »

Recently I decided to use SmugMug to store my photo galleries. They have an API people can use to access the features of the site programmatically and I noticed they added OAuth in the latest version. So, I thought I would give it a try and see if I can use the OAuth Support in Jersey to authorize and authenticate my client application with SmugMug. Turned out it works! Here is how to do it:

  1. First you have to request an API Key from SmugMug. You can do it here.
  2. They probably approve these automatically – mine was approved immediately and I got the key along with a “secret” (another number used as a consumer secret key in OAuth).
  3. Now you can create a new maven project, adding jersey-client, jersey-json, oauth-signature and oauth-client as the dependencies – here is a pom file snippet:
        <dependency>
          <groupId>com.sun.jersey</groupId>
          <artifactId>jersey-client</artifactId>
          <version>1.1.2-ea-SNAPSHOT</version>
        </dependency>
        <dependency>
          <groupId>com.sun.jersey</groupId>
          <artifactId>jersey-json</artifactId>
          <version>1.1.2-ea-SNAPSHOT</version>
        </dependency>
        <dependency>
          <groupId>com.sun.jersey.oauth</groupId>
          <artifactId>oauth-signature</artifactId>
          <version>1.1.2-ea-SNAPSHOT</version>
        </dependency>
        <dependency>
          <groupId>com.sun.jersey.oauth</groupId>
          <artifactId>oauth-client</artifactId>
          <version>1.1.2-ea-SNAPSHOT</version>
        </dependency>
  4. The first thing you need to do according to the OAuth spec is to get an unauthorized request token from the provider (SmugMug in this case). You will need the key and the secret you obtained in step 1. The SmugMug API provides a method for requesting these tokens – getRequestToken. Here is an example of how you can call this method using Jersey client API and the Jersey OAuth library:
    public class App {
        // base URL for the API calls
        private static final String URL_API =
                "http://api.smugmug.com/services/api/json/1.2.2/";
    
        private static final String CONSUMER_SECRET = /* your API Key */;
        private static final String CONSUMER_KEY = /* your secret key */;
    
        public static void main( String[] args ) throws Exception {
            // Create a Jersey client
            Client client = Client.create();
    
            // Create a resource to be used to make SmugMug API calls
            WebResource resource = client.resource(URL_API).
                    queryParam("method", "smugmug.auth.getRequestToken");
    
            // Set the OAuth parameters
            OAuthSecrets secrets = new OAuthSecrets().consumerSecret(CONSUMER_SECRET);
            OAuthParameters params = new OAuthParameters().consumerKey(CONSUMER_KEY).
                    signatureMethod("HMAC-SHA1").version("1.0");
            // Create the OAuth client filter
            OAuthClientFilter filter =
                    new OAuthClientFilter(client.getProviders(), params, secrets);
            // Add the filter to the resource
            resource.addFilter(filter);
    
            // make the request and print out the result
            System.out.println(resource.get(String.class));
        }
    }
  5. The next step in the OAuth flow is to obtain user authorization. To do this, the user needs to be redirected to the SmugMug authorization URL – http://api.smugmug.com/services/oauth/authorize.mg (see the SmugMug Specifics section on their OAuth page), passing the request token ID as a query parameter (you need to extract that from the getRequestToken method’s response). At this URL the user will log in and grant the requested access to your application. Here is how I did it:
    public class App {
        // base URL for the API calls
        private static final String URL_API =
                "http://api.smugmug.com/services/api/json/1.2.2/";
        // authorization URL
        private static final String URL_AUTHORIZE =
                "http://api.smugmug.com/services/oauth/authorize.mg";
    
        private static final String CONSUMER_SECRET = /* your API Key */;
        private static final String CONSUMER_KEY = /* your secret key */;
    
        public static void main( String[] args ) throws Exception {
            // Create a Jersey client
            Client client = Client.create();
    
            // Create a resource to be used to make SmugMug API calls
            WebResource resource = client.resource(URL_API).
                    queryParam("method", "smugmug.auth.getRequestToken");
    
            // Set the OAuth parameters
            OAuthSecrets secrets = new OAuthSecrets().consumerSecret(CONSUMER_SECRET);
            OAuthParameters params = new OAuthParameters().consumerKey(CONSUMER_KEY).
                    signatureMethod("HMAC-SHA1").version("1.0");
            // Create the OAuth client filter
            OAuthClientFilter filter =
                    new OAuthClientFilter(client.getProviders(), params, secrets);
            // Add the filter to the resource
            resource.addFilter(filter);
    
            // make the request
            RequestTokenResponse response = resource.get(RequestTokenResponse.class);
            // check the status
            if (!"ok".equals(response.stat)) {
                System.out.println("getRequestToken failed with response: " +
                        response.toString());
                return;
            }
    
            // open the browser at the authorization URL to let user authorize
            Desktop.getDesktop().browse(new URI(URL_AUTHORIZE +
                    "?oauth_token=" + response.auth.token.id));
        }
    }

    The RequestTokenResponse class representing getRequestToken method’s response looks as follows:

    @XmlRootElement
    public class RequestTokenResponse {
        public String stat;
        public String method;
        public @XmlElement(name="Auth") AuthElement auth;
    
        public static class AuthElement {
            public @XmlElement(name="Token") TokenElement token;
    
            @Override
            public String toString() {
                return "token=(" + (token == null ? "null" : token.toString()) + ")";
            }
        }
    
        public static class TokenElement {
            public String id;
            public @XmlElement(name="Secret") String secret;
    
            @Override
            public String toString() {
                return "id=" + id + " secret=" + secret;
            }
        }
    
        @Override
        public String toString() {
            return "stat=" + stat + " method=" + method + " auth=(" +
                    (auth == null ? "null" : auth.toString()) + ")";
        }
    }
  6. After the user authenticates and grants access for your application, the last step is to request an access token – that will then enable your application to make subsequent API calls. You can implement this by adding the following lines at the end of the main method from the previous bullet:
            // wait for the user to authenticate
            System.out.println("Once you authenticated with SmugMug and granted" +
                    "permissions to this app, press Enter to continue.");
            System.in.read();
    
            // make an API call to request the access token
            resource = client.resource(URL_API).queryParam("method",
                    "smugmug.auth.getAccessToken");
            // use the request token id and secret to create the request
            secrets.setTokenSecret(response.auth.token.secret);
            params.token(response.auth.token.id);
            resource.addFilter(filter);
            // make the request and print out the result
            System.out.println(resource.get(String.class));
  7. That’s it! Now your application can store the access token and use it to perform actions on behalf of the user.

14 comments

  1. Paul C. Bryan says:

    Hey Martin, glad to see the OAuth extensions worked for you.

  2. Suneel Marthi says:

    Hi Martin,

    Thanks for posting this blog. I am presently working on a prototype that needs to implement OAuth to secure RESTful Services.

    We are using Restlet on the project, but due to absence of any documentation and no working examples we had to ditch Restlet and switch over to Jersey.

    I need to implement both a Service Consumer (the example you have provided here) and Service Provider. Looking at the Jersey OAuth extension, seems like it only supports the Client APi (for the Consumer); am I right?

    Any pointers you could give me would be much appreciated.

    Thanks for your help,
    Suneel

    • Martin says:

      Hi Suneel,
      I am very glad Jersey works well for you. Paul and others are very responsive on the mailing list (users@jersey.dev.java.net) so I would encourage you to post any further questions there – that way others will be able to help you as well.
      The OAuth in Jersey definitely supports the server side as well. It is implemented in oauth-server module (in my example I am using oauth-signature and oauth-client, so in your case you would also depend on oauth-server). See http://wikis.sun.com/display/Jersey/OAuth for some basic docs, you may want to look at the oauth-tests under jersey/contribs/jersey-oauth in the svn repo to get a better idea.
      Hopefully this helps.
      Martin

    • Paul C. Bryan says:

      Hi Suneel:

      The OAuth Jersey extension proivdes server-side support for message signature validation. In the Jersey project, take a look at the com.sun.jersey.oauth.server.OAuthServerRequest class.

      As Martin mentioned, I’d encourage you to post on the users@jersey.dev.java.net mailing list, as we frequently check for messages there. To join the mailing list, go to https://jersey.dev.java.net/servlets/ProjectMailingListList (you need to be logged-in to subscribe to the list).

      Paul

  3. Suneel Marthi says:

    Thanks Paul and Martin,

    I am good now.

    Regards,
    Suneel

    • Elango says:

      Hi Suneel
      I am in need of some samples to implement the server side part of the OAuth. could you share some examples of how you verify the signature based on RSA-SHA1 if you are aware of any…

  4. Jeff says:

    Greetings,

    Do you happen to know if it is possible to get an authorization token without user interaction? I am working on a website and I want to be able to automatically create a new gallery on smugmug without the user having to authorize the access.

    Thanks in advance.

    Regards,
    J

  5. harish says:

    http://wikis.sun.com/display/Jersey/OAuth

    is broken.

    Can you please point me to some tutorials,resources on how to implement a service provider using oauth and jersey.

Leave a Reply to harish