{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Mining the Social Web, 2nd Edition\n", "\n", "## Appendix B: OAuth Primer\n", "\n", "This IPython Notebook provides an interactive way to follow along with and explore the numbered examples from [_Mining the Social Web (3rd Edition)_](http://bit.ly/Mining-the-Social-Web-3E). The intent behind this notebook is to reinforce the concepts from the sample code in a fun, convenient, and effective way. This notebook assumes that you are reading along with the book and have the context of the discussion as you work through these exercises.\n", "\n", "In the somewhat unlikely event that you've somehow stumbled across this notebook outside of its context on GitHub, [you can find the full source code repository here](http://bit.ly/Mining-the-Social-Web-3E).\n", "\n", "## Copyright and Licensing\n", "\n", "You are free to use or adapt this notebook for any purpose you'd like. However, please respect the [Simplified BSD License](https://github.com/mikhailklassen/Mining-the-Social-Web-3rd-Edition/blob/master/LICENSE) that governs its use.\n", "\n", "## Notes\n", "\n", "While the chapters in the book opt to simplify the discussion by avoiding a discussion of OAuth and instead opting to use application credentials provided by social web properties for API access, this notebook demonstrates how to implement some OAuth flows for several of the more prominent social web properties. While IPython Notebook is used for consistency and ease of learning, and in some cases, this actually adds a little bit of extra complexity in some cases given the nature of embedding a web server and handling asynchronous callbacks. (Still, the overall code should be straightforward to adapt as needed.)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Twitter OAuth 1.0a Flow with IPython Notebook\n", "\n", "Twitter implements OAuth 1.0A as its standard authentication mechanism, and in order to use it to make requests to Twitter's API, you'll need to go to https://dev.twitter.com/apps and create a sample application. There are three items you'll need to note for an OAuth 1.0A workflow, a consumer key and consumer secret that identify the application as well as the oauth_callback URL that tells Twitter where redirect back to after the user has authorized the application. Note that you will need an ordinary Twitter account in order to login, create an app, and get these credentials. Keep in mind that for development purposes or for accessing your own account's data, you can simply use the oauth token and oauth token secret that are provided in your appliation settings to authenticate as opposed to going through the steps here. The process of obtaining an the oauth token and oauth token secret is fairly straight forward (especially with the help of a good library), but an implementation in IPython Notebook is a bit tricker due to the nature of embedding a web server, capturing information within web server contexts, and handling the various redirects along the way.\n", "\n", "You must ensure that your browser is not blocking popups in order for this script to work.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 1. Twitter OAuth 1.0a Flow" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import json\n", "from flask import Flask, request\n", "from threading import Timer\n", "from IPython.display import IFrame\n", "from IPython.display import display\n", "from IPython.display import Javascript as JS\n", "\n", "import twitter\n", "from twitter.oauth_dance import parse_oauth_tokens\n", "from twitter.oauth import read_token_file, write_token_file\n", "\n", "OAUTH_FILE = \"/tmp/twitter_oauth\"\n", "\n", "# XXX: Go to http://twitter.com/apps/new to create an app and get values\n", "# for these credentials that you'll need to provide in place of these\n", "# empty string values that are defined as placeholders.\n", "# See https://dev.twitter.com/docs/auth/oauth for more information \n", "# on Twitter's OAuth implementation and ensure that *oauth_callback*\n", "# is defined in your application settings as shown below if you are \n", "# using Flask in this IPython Notebook\n", "\n", "# Define a few variables that will bleed into the lexical scope of a couple of \n", "# functions below\n", "CONSUMER_KEY = ''\n", "CONSUMER_SECRET = ''\n", "oauth_callback = 'http://127.0.0.1:5000/oauth_helper'\n", " \n", "# Setup a callback handler for when Twitter redirects back to us after the user authorizes the app\n", "\n", "webserver = Flask(\"TwitterOAuth\")\n", "@webserver.route(\"/oauth_helper\")\n", "def oauth_helper():\n", " \n", " oauth_verifier = request.args.get('oauth_verifier')\n", "\n", " # Pick back up credentials from ipynb_oauth_dance\n", " oauth_token, oauth_token_secret = read_token_file(OAUTH_FILE)\n", " \n", " _twitter = twitter.Twitter(\n", " auth=twitter.OAuth(\n", " oauth_token, oauth_token_secret, CONSUMER_KEY, CONSUMER_SECRET),\n", " format='', api_version=None)\n", "\n", " oauth_token, oauth_token_secret = parse_oauth_tokens(\n", " _twitter.oauth.access_token(oauth_verifier=oauth_verifier))\n", "\n", " # This web server only needs to service one request, so shut it down\n", " shutdown_after_request = request.environ.get('werkzeug.server.shutdown')\n", " shutdown_after_request()\n", "\n", " # Write out the final credentials that can be picked up after the blocking\n", " # call to webserver.run() below.\n", " write_token_file(OAUTH_FILE, oauth_token, oauth_token_secret)\n", " return \"%s %s written to %s\" % (oauth_token, oauth_token_secret, OAUTH_FILE)\n", "\n", "\n", "# To handle Twitter's OAuth 1.0a implementation, we'll just need to implement a custom\n", "# \"oauth dance\" and will closely follower the pattern defined in twitter.oauth_dance.\n", "\n", "def ipynb_oauth_dance():\n", " \n", " _twitter = twitter.Twitter(\n", " auth=twitter.OAuth('', '', CONSUMER_KEY, CONSUMER_SECRET),\n", " format='', api_version=None)\n", "\n", " oauth_token, oauth_token_secret = parse_oauth_tokens(\n", " _twitter.oauth.request_token(oauth_callback=oauth_callback))\n", "\n", " # Need to write these interim values out to a file to pick up on the callback from Twitter\n", " # that is handled by the web server in /oauth_helper\n", " write_token_file(OAUTH_FILE, oauth_token, oauth_token_secret)\n", " \n", " oauth_url = ('http://api.twitter.com/oauth/authorize?oauth_token=' + oauth_token)\n", " \n", " # Tap the browser's native capabilities to access the web server through a new window to get\n", " # user authorization\n", " display(JS(\"window.open('%s')\" % oauth_url))\n", "\n", "\n", "# After the webserver.run() blocking call, start the oauth dance that will ultimately\n", "# cause Twitter to redirect a request back to it. Once that request is serviced, the web\n", "# server will shutdown, and program flow will resume with the OAUTH_FILE containing the\n", "# necessary credentials\n", "Timer(1, lambda: ipynb_oauth_dance()).start()\n", "\n", "webserver.run(host='0.0.0.0')\n", "\n", "# The values that are read from this file are written out at\n", "# the end of /oauth_helper\n", "oauth_token, oauth_token_secret = read_token_file(OAUTH_FILE)\n", "\n", "# These 4 credentials are what is needed to authorize the application\n", "auth = twitter.oauth.OAuth(oauth_token, oauth_token_secret,\n", " CONSUMER_KEY, CONSUMER_SECRET)\n", " \n", "twitter_api = twitter.Twitter(auth=auth)\n", "\n", "print(twitter_api)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Facebook OAuth 2.0 Flow with IPython Notebook\n", "\n", "Facebook implements OAuth 2.0 as its standard authentication mechanism, and this example demonstrates how get an access token for making API requests once you've created an app and gotten a \"client id\" value that can be used to initiate an OAuth flow. Note that you will need an ordinary Facebook account in order to login, create an app, and get these credentials. You can create an app through the \"Developer\" section of your account settings as shown below or by navigating directly to https://developers.facebook.com/apps/. During development or debugging cycles, or to just access data in your own account, you may sometimes find it convenient to also reference the access token that's available to you through the Graph API Explorer tool at https://developers.facebook.com/tools/explorer as opposed to using the flow described here. The process of obtaining an access token is fairly straight forward, but an implementation in IPython Notebook is a bit tricker due to the nature of embedding a web server, capturing information within web server contexts, and handling the various redirects along the way.\n", "\n", "You must ensure that your browser is not blocking popups in order for this script to work.\n", "
\n", "
\n", "
\n", "Create apps at https://developers.facebook.com/apps/
\n", "
\n", "
\n", "Clicking on the app in your list to see the app dashboard and access app settings." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 2. Facebook OAuth 2.0 Flow" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import urllib\n", "from flask import Flask, request\n", "from threading import Timer\n", "from IPython.display import display\n", "from IPython.display import Javascript as JS\n", "\n", "# XXX: Get this value from your Facebook application's settings for the OAuth flow\n", "# at https://developers.facebook.com/apps\n", "\n", "APP_ID = '' \n", "\n", "# This value is where Facebook will redirect. We'll configure an embedded\n", "# web server to be serving requests here\n", "\n", "REDIRECT_URI = 'http://localhost:5000/oauth_helper'\n", "\n", "# You could customize which extended permissions are being requested for your app\n", "# by adding additional items to the list below. See\n", "# https://developers.facebook.com/docs/reference/login/extended-permissions/\n", "\n", "EXTENDED_PERMS = ['user_likes']\n", "\n", "# A temporary file to store a code from the web server\n", "\n", "OAUTH_FILE = 'resources/ch02-facebook/access_token.txt'\n", "\n", "# Configure an emedded web server that accepts one request, parses\n", "# the fragment identifier out of the browser window redirects to another\n", "# handler with the parsed out value in the query string where it can be captured\n", "# and stored to disk. (A webserver cannot capture information in the fragment \n", "# identifier or that work would simply be done in here.)\n", "\n", "webserver = Flask(\"FacebookOAuth\")\n", "@webserver.route(\"/oauth_helper\")\n", "def oauth_helper():\n", " return ''''''\n", "\n", "# Parses out a query string parameter and stores it to disk. This is required because\n", "# the address space that Flask uses is not shared with IPython Notebook, so there is really\n", "# no other way to share the information than to store it to a file and access it afterward\n", "@webserver.route(\"/access_token_capture\")\n", "def access_token_capture():\n", " access_token = request.args.get('access_token')\n", " f = open(OAUTH_FILE, 'w') # Store the code as a file\n", " f.write(access_token)\n", " f.close()\n", " \n", " # It is safe (and convenient) to shut down the web server after this request\n", " shutdown_after_request = request.environ.get('werkzeug.server.shutdown')\n", " shutdown_after_request()\n", " return access_token\n", "\n", "\n", "# Send an OAuth request to Facebook, handle the redirect, and display the access\n", "# token that's included in the redirect for the user to copy and paste\n", " \n", "args = dict(client_id=APP_ID, redirect_uri=REDIRECT_URI,\n", " scope=','.join(EXTENDED_PERMS), type='user_agent', display='popup'\n", " )\n", "\n", "oauth_url = 'https://facebook.com/dialog/oauth?' + urllib.parse.urlencode(args)\n", "\n", "Timer(1, lambda: display(JS(\"window.open('%s')\" % oauth_url))).start()\n", "\n", "\n", "webserver.run(host='0.0.0.0')\n", "\n", "access_token = open(OAUTH_FILE).read()\n", "\n", "print(access_token)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# LinkedIn OAuth 2.0 Flow with IPython Notebook\n", "LinkedIn implements OAuth 2.0 as one of its standard authentication mechanism, and \"Example 3\" demonstrates how to use it to get an access token for making API requests once you've created an app and gotten the \"API Key\" and \"Secret Key\" values that are part of the OAuth flow. Note that you will need an ordinary LinkedIn account in order to login, create an app, and get these credentials. You can create an app through the \"Developer\" section of your account settings as shown below or by navigating directly to https://www.linkedin.com/secure/developer.\n", "\n", "You must ensure that your browser is not blocking popups in order for this script to work.\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Example 3. Using LinkedIn OAuth credentials to receive an access token an authorize an application" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note: You must ensure that your browser is not blocking popups in order for this script to work. LinkedIn's OAuth flow appears to expressly involve opening a new window, and it does not appear that an inline frame can be used as is the case with some other social web properties. You may also find it very convenient to ensure that you are logged into LinkedIn at http://www.linkedin.com/ with this browser before executing this script, because the OAuth flow will prompt you every time you run it if you are not already logged in. If for some reason you cause IPython Notebook to hang, just select \"Kernel => Interrupt\" from its menu." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "import os\n", "from threading import Timer\n", "from flask import Flask, request\n", "from linkedin import linkedin # pip install python3-linkedin\n", "from IPython.display import display\n", "from IPython.display import Javascript as JS\n", "\n", "# XXX: Get these values from your application's settings for the OAuth flow\n", "\n", "CONSUMER_KEY = ''\n", "CONSUMER_SECRET = ''\n", "\n", "# This value is where LinkedIn will redirect. We'll configure an embedded\n", "# web server to be serving requests here. Make sure to add this to your\n", "# app settings\n", "REDIRECT_URL = 'http://localhost:5000/oauth_helper'\n", "\n", "# A temporary file to store a code from the web server\n", "OAUTH_FILE = 'resources/ch04-linkedin/linkedin.authorization_code'\n", "\n", "# These should match those in your app settings\n", "permissions = {'BASIC_PROFILE': 'r_basicprofile',\n", " 'EMAIL_ADDRESS': 'r_emailaddress',\n", " 'SHARE': 'w_share',\n", " 'COMPANY_ADMIN': 'rw_company_admin'}\n", "\n", "# Configure an emedded web server that accepts one request, stores a file\n", "# that will need to be accessed outside of the request context, and \n", "# immediately shuts itself down\n", "\n", "webserver = Flask(\"OAuthHelper\")\n", "@webserver.route(\"/oauth_helper\")\n", "def oauth_helper():\n", " code = request.args.get('code')\n", " f = open(OAUTH_FILE, 'w') # Store the code as a file\n", " f.write(code)\n", " f.close()\n", " shutdown_after_request = request.environ.get('werkzeug.server.shutdown')\n", " shutdown_after_request()\n", " return \"\"\"

Handled redirect and extracted code %s \n", " for authorization

\"\"\" % (code,)\n", "\n", "# Send an OAuth request to LinkedIn, handle the redirect, and display the access\n", "# token that's included in the redirect for the user to copy and paste\n", "\n", "auth = linkedin.LinkedInAuthentication(CONSUMER_KEY, CONSUMER_SECRET, REDIRECT_URL, \n", " permissions.values())\n", "\n", "# Display popup after a brief delay to ensure that the web server is running and \n", "# can handle the redirect back from LinkedIn\n", "\n", "Timer(1, lambda: display(JS(\"window.open('%s')\" % auth.authorization_url))).start()\n", "\n", "# Run the server to accept the redirect back from LinkedIn and capture the access\n", "# token. This command blocks, but the web server is configured to shut itself down\n", "# after it serves a request, so after the redirect occurs, program flow will continue\n", "\n", "webserver.run(host='0.0.0.0')\n", "\n", "# As soon as the request finishes, the web server shuts down and these remaining commands\n", "# are executed, which exchange an authorization code for an access token. This process\n", "# seems to need full automation because the authorization code expires very quickly.\n", "\n", "auth.authorization_code = open(OAUTH_FILE).read()\n", "auth.get_access_token()\n", "\n", "# Prevent stale tokens from sticking around, which could complicate debugging\n", "os.remove(OAUTH_FILE)\n", "\n", "\n", "# How you can use the application to access the LinkedIn API...\n", "app = linkedin.LinkedInApplication(auth)\n", "print(app.get_profile())" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.7.1" } }, "nbformat": 4, "nbformat_minor": 1 }