Skip to content

Commit dbe1ba2

Browse files
committedApr 12, 2010
Raw OAuth example to complement JavaScript SDK examples
1 parent be70b83 commit dbe1ba2

File tree

3 files changed

+193
-0
lines changed

3 files changed

+193
-0
lines changed
 

‎examples/oauth/app.yaml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
application: facebook-example
2+
version: 1
3+
runtime: python
4+
api_version: 1
5+
6+
handlers:
7+
- url: /.*
8+
script: facebookoauth.py

‎examples/oauth/facebookoauth.py

+168
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,168 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright 2010 Facebook
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
6+
# not use this file except in compliance with the License. You may obtain
7+
# a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
13+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
14+
# License for the specific language governing permissions and limitations
15+
# under the License.
16+
17+
"""A barebones AppEngine application that uses Facebook for login.
18+
19+
This application uses OAuth 2.0 directly rather than relying on Facebook's
20+
JavaScript SDK for login. It also accesses the Facebook Graph API directly
21+
rather than using the Python SDK. It is designed to illustrate how easy
22+
it is to use the Facebook Platform without any third party code.
23+
24+
See the "appengine" directory for an example using the JavaScript SDK.
25+
Using JavaScript is recommended if it is feasible for your application,
26+
as it handles some complex authentication states that can only be detected
27+
in client-side code.
28+
"""
29+
30+
FACEBOOK_APP_ID = "282162793089"
31+
FACEBOOK_APP_SECRET = "18c29d5c857d4bd19796239354642a11"
32+
33+
import base64
34+
import cgi
35+
import Cookie
36+
import email.utils
37+
import hashlib
38+
import hmac
39+
import os.path
40+
import time
41+
import urllib
42+
import wsgiref.handlers
43+
44+
from django.utils import simplejson as json
45+
from google.appengine.ext import db
46+
from google.appengine.ext import webapp
47+
from google.appengine.ext.webapp import util
48+
from google.appengine.ext.webapp import template
49+
50+
51+
class User(db.Model):
52+
id = db.StringProperty(required=True)
53+
created = db.DateTimeProperty(auto_now_add=True)
54+
updated = db.DateTimeProperty(auto_now=True)
55+
name = db.StringProperty(required=True)
56+
profile_url = db.StringProperty(required=True)
57+
access_token = db.StringProperty(required=True)
58+
59+
60+
class BaseHandler(webapp.RequestHandler):
61+
@property
62+
def current_user(self):
63+
"""Returns the logged in Facebook user, or None if unconnected."""
64+
if not hasattr(self, "_current_user"):
65+
self._current_user = None
66+
user_id = parse_cookie(self.request.cookies.get("fb_user"))
67+
if user_id:
68+
self._current_user = User.get_by_key_name(user_id)
69+
return self._current_user
70+
71+
72+
class HomeHandler(BaseHandler):
73+
def get(self):
74+
path = os.path.join(os.path.dirname(__file__), "oauth.html")
75+
args = dict(current_user=self.current_user)
76+
self.response.out.write(template.render(path, args))
77+
78+
79+
class LoginHandler(BaseHandler):
80+
def get(self):
81+
verification_code = self.request.get("code")
82+
args = dict(client_id=FACEBOOK_APP_ID, callback=self.request.path_url)
83+
if self.request.get("code"):
84+
args["client_secret"] = FACEBOOK_APP_SECRET
85+
args["code"] = self.request.get("code")
86+
response = cgi.parse_qs(urllib.urlopen(
87+
"https://graph.facebook.com/oauth/access_token?" +
88+
urllib.urlencode(args)).read())
89+
access_token = response["access_token"][-1]
90+
91+
# Download the user profile and cache a local instance of the
92+
# basic profile info
93+
profile = json.load(urllib.urlopen(
94+
"https://graph.facebook.com/me?" +
95+
urllib.urlencode(dict(access_token=access_token))))
96+
user = User(key_name=str(profile["id"]), id=str(profile["id"]),
97+
name=profile["name"], access_token=access_token,
98+
profile_url=profile["profile_url"])
99+
user.put()
100+
set_cookie(self.response, "fb_user", str(profile["id"]),
101+
expires=time.time() + 30 * 86400)
102+
self.redirect("/")
103+
else:
104+
self.redirect(
105+
"https://graph.facebook.com/oauth/authorize?" +
106+
urllib.urlencode(args))
107+
108+
109+
class LogoutHandler(BaseHandler):
110+
def get(self):
111+
set_cookie(self.response, "fb_user", "", expires=time.time() - 86400)
112+
self.redirect("/")
113+
114+
115+
def set_cookie(response, name, value, domain=None, path="/", expires=None):
116+
"""Generates and signs a cookie for the give name/value"""
117+
timestamp = str(int(time.time()))
118+
value = base64.b64encode(value)
119+
signature = cookie_signature(value, timestamp)
120+
cookie = Cookie.BaseCookie()
121+
cookie[name] = "|".join([value, timestamp, signature])
122+
cookie[name]["path"] = path
123+
if domain: cookie[name]["domain"] = domain
124+
if expires:
125+
cookie[name]["expires"] = email.utils.formatdate(
126+
expires, localtime=False, usegmt=True)
127+
response.headers._headers.append(("Set-Cookie", cookie.output()[12:]))
128+
129+
130+
def parse_cookie(value):
131+
"""Parses and verifies a cookie value from set_cookie"""
132+
if not value: return None
133+
parts = value.split("|")
134+
if len(parts) != 3: return None
135+
if cookie_signature(parts[0], parts[1]) != parts[2]:
136+
logging.warning("Invalid cookie signature %r", value)
137+
return None
138+
timestamp = int(parts[1])
139+
if timestamp < time.time() - 30 * 86400:
140+
logging.warning("Expired cookie %r", value)
141+
return None
142+
try:
143+
return base64.b64decode(parts[0]).strip()
144+
except:
145+
return None
146+
147+
148+
def cookie_signature(*parts):
149+
"""Generates a cookie signature.
150+
151+
We use the Facebook app secret since it is different for every app (so
152+
people using this example don't accidentally all use the same secret).
153+
"""
154+
hash = hmac.new(FACEBOOK_APP_SECRET, digestmod=hashlib.sha1)
155+
for part in parts: hash.update(part)
156+
return hash.hexdigest()
157+
158+
159+
def main():
160+
util.run_wsgi_app(webapp.WSGIApplication([
161+
(r"/", HomeHandler),
162+
(r"/auth/login", LoginHandler),
163+
(r"/auth/logout", LogoutHandler),
164+
]))
165+
166+
167+
if __name__ == "__main__":
168+
main()

‎examples/oauth/oauth.html

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
2+
<html xmlns="http://www.w3.org/1999/xhtml">
3+
<head>
4+
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
5+
<title>Facebook OAuth Example</title>
6+
</head>
7+
<body>
8+
{% if current_user %}
9+
<p><a href="{{ current_user.profile_url }}"><img src="http://graph.facebook.com/{{ current_user.id }}/picture"/></a></p>
10+
<p>You are logged in as {{ current_user.name|escape }}</p>
11+
<p><a href="/auth/logout">Log out</a></p>
12+
{% else %}
13+
<p>You are not yet logged into this site</p>
14+
<p><a href="/auth/login">Log in with Facebook</a></p>
15+
{% endif %}
16+
</body>
17+
</html>

0 commit comments

Comments
 (0)