Using Python To Build A Social Network On Top Of The Janrain Platform (Part 2)

In the first tutorial in this series, I built some of the very basic rudiments of a social networking web app called Brainbook. Brainbook is essentially a lightweight Facebook clone for people who want to list their academic interests and show them to their friends on their profile pages. The app that I develop here won’t be terribly sophisticated on its own, but I think it’s a good way to learn some of the basics of implementing some of Janrain’s core functionality—e.g. social login—as well as wading into some more advanced territory.

The first tutorial left off with our Python Flask server being capable of fetching a user’s temporary token from the Janrain server. But a token by itself isn’t terribly useful (unless you have an unhealthy fascination with long random numbers). Now, we need to actually use that token to fetch user info from Janrain. From there, we can begin putting that info to work.

Continuing Our Server Setup

Once our widget is working properly and we know that user tokens are being retrieved from Janrain (remember that the last step in the first tutorial was making the token appear on the page), we can begin interacting with the Janrain Engage server. At this stage in the development of Brainbook, we will be using Twitter information only (for the sake of simplicity). Later on, we’ll add other identity providers to the mix.

In order to fetch user information from Janrain, we need to send two important pieces of information to the Janrain server: the API key for our Janrain application and the temporary user token obtained by our sign-in widget. On our server, we’ll construct a dictionary—referred to as a hash in most other languages—out of two elements: the API key for our Janrain application and the token that we just retrieved for this user. Note that this user token is not something that we’ll want to store anywhere. It changes every time a user logs in and is meant to simply foster communication between our app and the Janrain platform. I’ll call that dictionary engage_api_params:

engage_api_params = {
    'apiKey': engage_api_key,
    'token': token,
    'format': 'json'
}

Our engage_api_key was set on our server in the last tutorial (all Engage-enabled applications use the same endpoint, so it’s perfectly okay for that value to be public). Personally, I’m a fan of storing these kinds of variables as environment variables, at least in development:

engage_api_key = os.environ[‘JANRAIN_ENGAGE_API_KEY’]

The token in the above dictionary is of course the recently retrieved temporary user token, which was POSTed to our /engage_callback_url. With this dict in hand, we need to request user information from the Janrain server. Fortuantely, Python has one of the greatest HTTP libraries ever in Kenneth Reitz’s Requests module. We need to import that module (by simply adding import requests at the top of our server.py file) and then add our HTTP request and some other stuff to our engage_callback() function:

user_data = requests.post(janrain_engage_root_url, params=engage_api_params)

This line pings the Janrain Engage server and passes the parameters we specified above (note that although we are retrieving information from the Janrain server, GET requests are highly deprecated in the Janrain API, and so we need to make a POST request).

The response will be stored in a variable called user_data. Now, we need to convert that data to JSON and extract the profile value from that:

auth_info = user_data.json()

This auth_info variable houses all of the user information that we just retrieved from Janrain. If we set up a view (more on that in a second) to simply display the raw data associated with this variable, it might look something like this:

{
    'stat': 'ok',
    'profile': {
        'providerName': 'Twitter',
        ...
    }
}

We can do anything we’d like with any of this information, but let’s keep it simple and simply fetch the user’s name.

username = auth_info['profile']['name']['formatted']

Now, let’s create a view that actually welcomes the user by name upon sign-in (in a logged_in.html file):

Welcome to the site, {{ username }}

Then, we’ll render that template and pass the username into it:

return render_template(‘welcome.html’, username=username)

At this point, our route will look like this:

@app.route('/engage_callback_url', methods=['POST'])
def engage_callback():
    token = request.form['token']
    engage_api_params = {
        'apiKey': engage_api_key,
        'token': token,
        'format': 'json'
    }
    user_data = requests.post(janrain_engage_root_url, params=engage_api_params)
    auth_info = user_data.json()
    username = auth_info['profile']['name']['formatted']
    return render_template('logged_in.html', username=username)

Greeting a user by name is nice, but remember that there’s plenty of other user data available once a call to the Engage API has been made successfully. Since we’re using Twitter, we also have access to the following information:

  • providerName (in this case Twitter)
  • identifier (the user’s unique Twitter URL)
  • preferredUsername (Twitter username)
  • displayName (usually the same as their formatted name)
  • name
  • formatted (first and last name, e.g. “John Smith”)
  • url (something along the lines of twitter.com/username)
  • photo (URL for the user’s Twitter photo)
  • address (their listed location, usually a city and state or city and country)

For something like Brainbook, which is a social network, it would be nice to use the user’s Twitter picture on their welcome page, just so that they feel even more at home. So let’s fetch that variable…

photo_url = auth_info['profile']['photo']

…and then modify our logged_in.html file from before to allow for that:

Welcome to the site, {{ username }}
<img src="{{ photo_url }}" />

If yours truly logs into the site using Twitter, I’ll be greeted like this:

Screenshot 2013-10-29 11.11.52

Storing Temporary User Info in a Browser Session

Our temporary user token expires pretty quickly, and we may want the user information that we retrieve with it to persist until the user signs out, but without necessarily storing that information in a database like Postgres or Riak. Flask makes storing session data incredibly easy. All that you have to do in terms of setup is to provide your application with a secret_key and then store session values in a session object.

Creating a random secret_key for your app can be done in a variety of ways, but in our case it will be easiest to do so in the Python shell. The os library (one of Python’s core libraries) has a urandom function that allows you to produce arbitrarily long byte strings. We can produce a 40-character byte string like so:

>>> import os
>>> os.urandom(40)

That will output something like this:

't4xf3xb5xd8xa62xe7xa9xb9xf1xec_x99xf5x90xd8^x19xeax07x7fxe0xe3x15x8dxa6x84x8fexb6xabqx8d\jx05xe6xJ'

We need to store that value in our app variable:

app.secret_key = 't4xf3 ... x05xe6xJ'

Once our app has a secret key, storing, using, and deleting session data is simply a matter of modifying our session dictionary:

session['key'] = 'value'

If we want to study the user’s name and photo URL in a session, all that we have to do is add the following to our /engage_callback_url route from above:

session['username'] = auth_info['profile']['name']['formatted']
session['photo_url'] = auth_info['profile']['photo']

Now, we can use that session data in conjunction with other routes in our app. In fact, we can even modify our views to rely on session variables (e.g. session['username'] instead of just username). Later on, we’ll add a more robust session storage capability using Redis, as relying on Flask’s native session storage mechanism means that user sessions cannot persist when the app goes down or is restarted. But this gets us off to a good start.

What’s Next

At this point, we’re beginning to have a more solid basis for a social network. We can fetch users’ publicly available Twitter information and use it to greet them with their name and photo when they’ve logged in successfully, and we can now store that information in client-side sessions. In the next tutorial, we’ll get our app hooked up to a database so that we can begin gathering user information to buttress the user information that our app can now fetch from Twitter.

Until next time!