Sunday, October 31, 2010

C2DM: Using ClientLogin to Obtain an Authentication Token

As explained in a previous entry, Google's Cloud to Device Messaging (C2DM) framework allows our Android applications to make use of the connection that Google services establishes with Android phones, meaning we can implement push notifications more easily and make our applications near real-time.  But where do we start?  Well, there's two sides that are needed...there's the Android device side, and there's the server side.  This blog entry is going to cover what is needed on the server side so that we can then start sending messages to the Android devices.

Setting The Stage
The first step is to create a new Google email account.  In order to send notifications, the web server needs to be logged in to a valid Google account, and so we simply set a new one up that is specifically for the server application's use only.

Once that has been set up, we then need to request access to the service itself.  At the time of writing, C2DM is in Google Labs, meaning it's necessary to signup to use it; this is easily done via this simple form.  You'll need to provide the package name of the Android app that will be receiving the messages, and you'll need to specify the Role Account Email as the new email account that we just set up.  From my experience, the registration process is quick and painless, with a confirmation email arriving within minutes.

Authenticating
With those prerequisites complete, our server will need to authenticate itself by using an authentication token, and we get one of those from ClientLogin.  Authentication tokens are used as it would be a bad idea to store the actual account credentials on the server, but if it's a bad idea to store the credentials, how does the app log in in the first place to obtain this authentication token?  Well, what we can do is create a very simple servlet that allows us to enter the email and password manually...this then gets sent to ClientLogin for validation, and if the credentials are good, then we should receive back an authentication token that is good for about a day (don't worry, the C2DM framework will provide us with a new token when the current one is about to expire).

To authenticate, we first need a connection object to https://www.google.com/accounts/ClientLogin:

HttpURLConnection connection = null;
try {
    URL url = null;
    try {
        url = new URL("https://www.google.com/accounts/ClientLogin");
    } catch (MalformedURLException e) {
        throw new ServletException(e.getCause());
    }
    connection = (HttpURLConnection) url.openConnection();
    connection.setDoOutput(true);
    connection.setUseCaches(false);
    connection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");
} catch (Exception e) {
    // Exception handling
}

The more observant may have noticed the use of HttpURLConnection instead of a HttpsURLConnection object.  Well, that's because I'm using Google App Engine to host my app, and HttpsURLConnection isn't a permitted class (although from my understanding, under the covers App Engine uses HttpsURLConnection for secure connections).
 
Now that we have our connection object, it's time to pass in the credentials that we wish to authenticate with:

StringBuilder sb = new StringBuilder(); 

addEncodedParameter(sb, "accountType", "GOOGLE");
addEncodedParameter(sb, "Email", request.getParameter("account"));
addEncodedParameter(sb, "Passwd", request.getParameter("password"));
addEncodedParameter(sb, "service", "ac2dm");
addEncodedParameter(sb, "source", "myCompany-demoApp-1.0.0");
String data = sb.toString();

DataOutputStream stream = new DataOutputStream(connection.getOutputStream());
stream.writeBytes(data);
stream.flush();
stream.close();

As you can see, this is where we're passing the account type, the email address, and the password.  The service parameter must be the Android C2DM service ("ac2dm"), and the source parameter should follow the format of "companyName-applicationName-versionID".  Oh, and the addEncodedParameter() method that I'm using?  That's just a simple helper method as shown below. :)

public void addEncodedParameter(StringBuilder sb, String name, String value) {

    if (sb.length() > 0) {
        sb.append("&");
    }

    try {
        sb.append(URLEncoder.encode(name, "UTF-8"));
        sb.append("=");
        sb.append(URLEncoder.encode(value, "UTF-8"));
    } catch (UnsupportedEncodingException e) {
    }
}
After we've sent the credentials, the next step is to process the response.  Basically, we're looking for a line in the response that starts with "Auth=", as this contains our token.  We're also going to capture any errors that we may have received:

BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream())); 

String line;
String tokenIdentifier = "Auth=";
String errorIdentifier = "Error=";
String token = null;
StringBuilder errors = new StringBuilder();
while ((line = reader.readLine()) != null) {
    if (line.startsWith(tokenIdentifier)) {
        token = line.substring(tokenIdentifier.length());
    } else if (line.startsWith(errorIdentifier)) {
        String error = line.substring(errorIdentifier.length());
        errors.append(error + System.getProperty("line.separator"));
    }
}
reader.close(); 
And that's about it.  Of course, this is just the bare minimum that is required to get an authentication token, as the code doesn't even attempt to handle CAPTCHA challenge responses.  In this event, our servlet must then display the CAPTCHA image url (http://www.google.com/accounts/ + CaptchaUrl) and prompt for the additional user input.  It can then respond to the challenge using another call and providing the additional headers logintoken and logincaptcha.

But for now, this should be enough to get a valid authentication token, and with the token in hand, we can start to send messages to our Android devices. :)

No comments:

Post a Comment