Skip to: Navigation | Content | Sidebar | Footer


Weblog Entry

Authenticating the Google Reader API

December 11, 2008

Last week I finally got around to doing something I’ve long intended: I moved all my RSS feeds into Google Reader and finally said goodbye to Bloglines. More interesting things have been coming from Google’s direction lately, and though there’s a public beta of a new version of Bloglines it’s been feeling stagnant. The final tipping points for me were the ASP errors the mobile version’s byte-trimming service has been issuing up for the past few weeks. So I switched, and after a very brief learning curve, I’m glad I did.

Aside from a nice iPhone-optimized version, one of the new toys I picked up in the switch is an unofficial Google Reader API. It’s been in an unofficial state for a whole three years now, with no sign of an actual release, so documentation is sparse at best. This wiki seems to be the definitive source, and for non-programmers like myself, it’s mind-bendingly vague.

One of the speculative reasons for the lack of an official release is the authentication currently necessary to log in and start using the API, and that proved to be exactly what I’ve spent the past few days banging my head against. Unless my Google-fu has weakened, there doesn’t appear to be much publicly-available code for using the API, and virtually nothing in PHP. So I figured I’d share what I came up with.

This is a script for logging in with your own account and pulling out your latest unread items in the form of an Atom feed. Drop the script on your server, change the login id and password, and it should work as intended. I haven’t explored much yet, and probably will never do anything beyond read-only, so you’re on your own past this point. But hopefully it’ll save someone a few hours anyway.

// ----------------------------------------
// Google Reader Authentication in PHP
// a basic script to get you in the door of 
// Google's unofficial Reader API
// by Dave Shea, mezzoblue.com
// ----------------------------------------

// cobbled together from notes on:
// http://code.google.com/p/pyrfeed/wiki/GoogleReaderAPI

// these are the urls we'll need to access various services
$urlAuth = "https://www.google.com/accounts/ClientLogin";
$urlAtom = "http://www.google.com/reader/atom";

// our array of login data
$login = array(
  "service" => "reader",
  "continue" => "http://www.google.com/",

  // Google id-only of the account holder
  // ie. for example@gmail.com, just use example
  "Email" => "google-id", 

  // the account's password in plaintext
  "Passwd" => "password",

  // an identifying name for your script, can be anything
  "source" => "my reader script",
);


// first step is to authenticae
// let's build a POST request using the login data array
$postRequest = "";
foreach($login as $field => $value) {
  $postRequest .= $field . "=" . $value . "&";
}

// start buffering what we get back
ob_start();
$ch = curl_init($urlAuth);
curl_setopt ($ch, CURLOPT_POST, true);
curl_setopt ($ch, CURLOPT_POSTFIELDS, $postRequest);
curl_setopt ($ch, CURLOPT_FOLLOWLOCATION, true);
curl_exec ($ch);
curl_close ($ch);
// throw the buffer into a variable
$loginResult = ob_get_contents(); 
ob_end_clean();

// we just received three lines of ugliness to contend with.
// each line is a huge string preceded with an ID
// the IDs are: SID, LSID, and Auth; we only want SID
// let's use some string parsing to weed it out
if ($i = strstr($loginResult, "LSID")) {
  $SID = substr($loginResult, 0, 
    (strlen($loginResult) - strlen($i)));
  $SID = rtrim(substr($SID, 4, (strlen($SID) - 4)));
}
// so we've found the SID
// now we can build the cookie that gets us in the door
$cookie = "SID=" . $SID . 
  "; domain=.google.com; path=/; expires=1600000000";


// this builds the action we'd like the API to perform
// in this case, it's getting our list of unread items
$action = $urlAtom . 
  "/user/-/state/com.google/reading-list";
  // note that the hyphen above is a shortcut
  // for "the currently logged-in user"


// start buffering what we get back
ob_start(); 
$ch = curl_init();
curl_setopt ($ch, CURLOPT_URL, $action);
curl_setopt ($ch, CURLOPT_HTTPGET, true);
curl_setopt ($ch, CURLOPT_COOKIE, $cookie);
curl_exec ($ch);
curl_close ($ch);
// throw the buffer into a variable
$xml = ob_get_contents();
ob_end_clean();

// and finally, let's take a look.
echo $xml;

Update: In mid 2010, Google changed the way authentication happens. This code example shows a working authentication process with the new system.


1
Daryl says:
December 11, 15h

This is only vaguely related to the article, but the excellent Bylines iPhone app is very much recommended: if you do substantial on the go reading it’s a much more robust option than the mobile site.

December 11, 15h

I started using Google Reader a few days ago as opposed to having a lot of little widgets on Google IG. I have also been thinking a lot about trying to get my recent items onto my blog. I haven’t had a chance to implement it yet, but I’ll definitely refer back to this when I do.

Robert says:
December 12, 08h

@Sam, I don’t know if you mean all your recently read items in Google Reader or something else, but I’ve recently been experimenting with using Google Reader’s “Share with Note” as one way of blogging. I’ve found that a lot of blog posts I want to make are really my thoughts on an article. It turns out that there is an atom feed of your shared items that you can get by clicking on “Shared items,” then the “at this web page” link. I think that is a list of all your shared items, not just shared with notes.

So, instead of all your recent reading (I know mine is several hundred per day), you can just share stuff and have it show up on your page with an atom parser. They also have a bookmarklet that will allow you to share any page in Google Reader (it even populates the text to share with what you highlight on the page).

Needless to say, those features have been quite helpful to me as I’ve been developing my new (unlaunched) site.

Thomas says:
December 12, 10h

Speaking of RSS feeds, the feed for your blog is currently broken. You’ve got a “]]>” at the end of the description section for this post, but without a matching “

5
mordisko says:
December 16, 02h

I’m getting this:

Redirection

Further action needs to be taken by your user agent in order to fulfill the request.

I supose it has something to do with extra-information needed

Dave S. says:
December 16, 13h

@mordisko - it should work just fine if you drop it on your server and change the Email and Passwd values to a valid Google Reader account. Did you do that part? Is it still not working?

(The error you’re getting is the same unhelpful message I kept getting myself during development, by the way)

January 13, 02h

Read it - tested it, and launched it… many thanks for the reference - this works like a charm for me!

Davd says:
January 19, 15h

A question re Robert’s reply (#3) above:

I’ve been trying to do the same thing, but am having trouble syndicating the Google Shared Items atom feed without a fully-qualified url (i.e. with an .xml, .rss, or .rdf extension). Fully-qualified url feeds are a requirement of Drupal’s Aggregator module, which I’m hoping to use for deploying on my own site.

Any thoughts?

David says:
January 19, 21h

Resolved the issue above with an atom parser module in Drupal: http://drupal.org/node/326601

Apologies for posting a Drupal-specific issue to this more general discussion.

April 12, 01h

I ended up here while searching for Google Reader API… but I had up comment on Daryl’s comment (#1) which, if you think about it for a little bit, you might understand how these two things relate… :p

@Daryl
After looking into several GR iPhone apps, I was really about to purchase Bylines… But gladly I read some reviews at the AppStore that saved me $5! An RSS app that doesn’t allow reading by individual feed is *unacceptable*!! Add to that the fact that it doesn’t save your window position/location (not alone on this, but a huge FAIL for any iPhone app giving the constant necessity of jumping between apps and no background support) and I think that charging $5 for it is not wrong, but paying $5 for it is from a purely economic perspective…

As to why I was looking for GR API documentation… :)