OAuth With Twitter and Python Flask

Here’s a couple tips on getting OAuth to work with Python Flask.

First, Flask has bunch of awesome extensions that make coding with Flask so easy. The two I am using for an OAuth login system are Flask-OAuth (obviously), and Flask-Login. The database I am using is Postgres, with the great Flask-SQLAlchemy ORM.

The User class I am using just needs 3 properties:

  • username
  • token
  • secret

For Flask-OAuth, most of the default configuration works as described. However, I did have to change the request_token_url, access_token_url and authorize_url to use https. The access_token_url should point to “https://api.twitter.com/oauth/authenticate” for processing the login.

I had trouble getting it to recognize the callback URL. I kept getting this error: raise OAuthException('Failed to generate request token'). I gave up debugging it, and just added the correct callback URL in the settings in Twitter.

Using Flask-Login is also straight forward. I initially didn’t know how to make enable a User class to use the login system, turns out it’s just a four functions that have to be included in the class:

1
2
3
4
5
6
7
8
9
10
11
def get_id(self):
  return self.id

def is_authenticated(self):
  return True

def is_active(self):
  return True

def is_anonymous(self):
  return False

The glue that makes the two play nicely together is all in these three functions:

1
2
3
4
5
6
@app.route('/login')
def login():
  if current_user.is_authenticated():
      return redirect('/')
  return twitter.authorize(callback=url_for('oauth_authorized',
      next=request.args.get('next') or request.referrer or None))

Before I send a Twitter OAuth request, I make sure the current_user is not authenticated. If I didn’t the OAuth would fail. Not sure why yet.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@app.route('/oauth-authorized')
@twitter.authorized_handler
def oauth_authorized(resp):
  next_url = request.args.get('next') or url_for('index')
  if resp is None:
      return redirect(next_url)

  this_account = Account.query.filter_by(username = resp['screen_name']).first()
  if this_account is None:
      new_account = Account(resp['screen_name'], "", resp['oauth_token'], resp['oauth_token_secret'])
      db.session.add(new_account)
      db.session.commit()
      login_user(new_account)
  else:
      login_user(this_account)

  return redirect(next_url)

What the callback handler does:

Once the authentication is complete, it looks in the database for the Twitter username. If it’s not found, it creates a new account and calls login_user(). If it is, it uses the returned account object to login the user.

1
2
3
4
5
6
@twitter.tokengetter
def get_twitter_token():
  if current_user.is_authenticated():
      return (current_user.token, current_user.secret)
  else:
      return None

This third function is self-explanatory.

Now, it’s as easy adding the @login_required decorator before a secure page function.

Comments