Anda di halaman 1dari 216

Open Stack: Hacking the

Social Web with Python

Thursday, 18 February 2010


Who are you?

Joe Stump Mike Malone


@joestump @mjmalone

Thursday, 18 February 2010


A special thanks to ...

David Recordon
@daveman692

Thursday, 18 February 2010


Reduce HTTP Requests

Bundle JavaScript and CSS


Use sprites for images
Reduce images / outside objects

Thursday, 18 February 2010


Introductions

Thursday, 18 February 2010


“Open data is
increasingly important
as services move
online.” – Tim Oʼreilly (OSCON, 2007)

Thursday, 18 February 2010


“65% of the worldwide
internet audience
visited at least one
social network last
month.” – comScore

Thursday, 18 February 2010


Rules of Web 2.0
1. The perpetual beta becomes a process for
engaging customers.
2. Share and share-alike data, reusing others’ and
providing APIs to your own.
3. Ignore the distinction between client and server.
4. On the net, open APIs and standard protocols win.
5. Lock-in comes from data accrual, owning a
namespace, or non-standard formats.

Thursday, 18 February 2010


Rules of Web 2.0
1. The perpetual beta becomes a process for
engaging customers.
2. Share and share-alike data, reusing others’ and
providing APIs to your own.
3. Ignore the distinction between client and server.
4. On the net, open APIs and standard protocols win.
5. Lock-in comes from data accrual, owning a
namespace, or non-standard formats.

Thursday, 18 February 2010


www

Thursday, 18 February 2010


Web 2.0

Thursday, 18 February 2010


?

Thursday, 18 February 2010


c:\

Thursday, 18 February 2010


http://

Thursday, 18 February 2010


• The rise of the API
• Websites started integrating data from
other websites
• Entire businesses were built on top of APIs
• Websites like to be social too

Thursday, 18 February 2010


http://

Thursday, 18 February 2010


http://xkcd.com/256/

Thursday, 18 February 2010


“Social networks will
be like air.” – Charlene Li, Forrester

http://blogs.forrester.com/groundswell/2008/03/the-future-of-s.html

Thursday, 18 February 2010


• Each API had its own mechanisms for
authentication
• Each API had its own data formats
• Few, if any, APIs had the ability to delegate
access to third parties

Thursday, 18 February 2010


http://

Thursday, 18 February 2010


I have an
idea!

Thursday, 18 February 2010


Identity Relationships Content+Activity

✓Profiles ✓Friends ✓Photos


✓Accounts ✓Followers ✓Blogs
✓Colleagues ✓Pokes
✓Favorites

Thursday, 18 February 2010


Identity Relationships Activity

Thursday, 18 February 2010


The open stack is a set of technologies that work together
to democratize and decentralize key pieces of the social
web.
Open stack technologies allow users to share their identity,
relationships, activity, and content across applications
and platforms.

Thursday, 18 February 2010


Great! How the hell
does it work?

Thursday, 18 February 2010


Cooperation

Thursday, 18 February 2010


“The nice thing about
standards is that you
have so many to
choose from.”
– Andrew S. Tanenbaum

Thursday, 18 February 2010


OAuth OpenID WebFinger
FOAF XFN Portable Contacts
LRDD XRD-Simple Yadis
microformats oEmbed Salmon
Atom Activity Streams PubSubHubBub
DiSo oExchange JSON
RDF RSS XML
XMPP CSS

Thursday, 18 February 2010


The open stack for the social
web is built entirely on HTTP.

Thursday, 18 February 2010


HTTP urllib, urllib2, httplib, httplib2

JSON simplejson, json-py, cjson

ElementTree, xml.dom, xml.dom.minidom,


XML
xml.sax, lxml, simplexml

Atom FeedParser

HTML BeautifulSoup, html5lib

URI cgi, urllib, urlparse

Thursday, 18 February 2010


Cryptographic
Signatures for Dummies

Thursday, 18 February 2010


Alice wants to make sure a message, delivered
over an insecure channel, came from Bob. Bob
and Alice get together and come up with a
“shared secret.” They randomly select
“crocodile” as their shared secret.

Thursday, 18 February 2010


>>> key = 'crocodile'
>>> message = 'Hi, Alice. You rock!'
>>> import hashlib
>>> import hmac
>>> hashed = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> '&'.join((message, hashed))
'Hi, Alice. You rock!&d2810dea6e29258ff89bbd6ec7c7ba21b63a8b32'

Thursday, 18 February 2010


>>> key = 'crocodile'
>>> message = 'Hi, Alice. You rock!'
>>> import hashlib
>>> import hmac
>>> hashed = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> '&'.join((message, hashed))
'Hi, Alice. You rock!&d2810dea6e29258ff89bbd6ec7c7ba21b63a8b32'

Thursday, 18 February 2010


>>> key = 'crocodile'
>>> message = 'Hi, Alice. You rock!'
>>> import hashlib
>>> import hmac
>>> hashed = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> '&'.join((message, hashed))
'Hi, Alice. You rock!&d2810dea6e29258ff89bbd6ec7c7ba21b63a8b32'

Thursday, 18 February 2010


>>> key = 'crocodile'
>>> message = 'Hi, Alice. You rock!'
>>> import hashlib
>>> import hmac
>>> hashed = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> '&'.join((message, hashed))
'Hi, Alice. You rock!&d2810dea6e29258ff89bbd6ec7c7ba21b63a8b32'

Thursday, 18 February 2010


>>> message, hash = received_message.split('&')
>>> print message
Hi, Alice. You rock!
>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> assert hash == check_hash
>>>

Thursday, 18 February 2010


>>> message, hash = received_message.split('&')
>>> print message
Hi, Alice. You rock!
>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> assert hash == check_hash
>>>

Thursday, 18 February 2010


>>> message, hash = received_message.split('&')
>>> print message
Hi, Alice. You rock!
>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> assert hash == check_hash
>>>

Thursday, 18 February 2010


>>> message, hash = received_message.split('&')
>>> print message
'Hi, Alice. You stink!'
>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> assert hash == check_hash
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
>>>

Thursday, 18 February 2010


>>> message, hash = received_message.split('&')
>>> print message
'Hi, Alice. You stink!'
>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()
>>> assert hash == check_hash
Traceback (most recent call last):
File "<stdin>", line 1, in <module>
AssertionError
>>>

Thursday, 18 February 2010


Identity

Thursday, 18 February 2010


A decentralized mechanism
for single sign-on

Thursday, 18 February 2010


OpenID was created by Brad Fitzpatrick and
David Recordon while working at Six Apart.
Their goal was to create a decentralized
authentication mechanism for blog comments.

Thursday, 18 February 2010


Problems Addressed

• Allows a user to sign into multiple websites


with a single password.
• Centralized identity removes potential land
grabs for per-site usernames.
• Profile duplication can be avoided if the
OpenID provider supports it.

Thursday, 18 February 2010


Why do I care?
Well over 500,000,000 accounts from
AOL, Google,Yahoo!, MySpace, Facebook,
and others are OpenID enabled.

Thursday, 18 February 2010


Terminology
• An IDENTIFIER is an HTTP URI or an XRI.
• A RELYING PARTY (RP) as an application
wishing to identify a user or prove
ownership of an identity.
• An OPENID PROVIDER (OP) is a server
that the RP relies on to assert identity of a
given user.

Thursday, 18 February 2010


How’s it work?

• An OpenID is just a URL. My OpenID is


http://stu.mp.
• You “claim” an OpenID (e.g. login or
register) with relying party.
• The “relying party” relies on the “provider”
to authenticate the user.

Thursday, 18 February 2010


1. USER identifies themselves to a RELYING
PARTY using their OpenID

Thursday, 18 February 2010


http://stu.mp

1. USER identifies themselves to a RELYING


PARTY using their OpenID

Thursday, 18 February 2010


<!DOCTYPE html>
<html>
<head>
<title>OpenID Example</title>
<link rel=”openid.server” href=”http://openid.example.com/
op/” />
<link rel=”openid2.provider” href=”http://openid.example.com/
op/” />
</head>
<body>
<h1>Hi, I’m Mike Malone. I think Ruby rules!</h2>
</body>
</html>

2. RELYING PARTY discovers OPENID


PROVIDER

Thursday, 18 February 2010


<!DOCTYPE html>
<html>
<head>
<title>OpenID Example</title>
<link rel=”openid.server” href=”http://openid.example.com/
op/” />
<link rel=”openid2.provider” href=”http://openid.example.com/
op/” />
</head>
<body>
<h1>Hi, I’m Mike Malone. I think Ruby rules!</h2>
</body>
</html>

2. RELYING PARTY discovers OPENID


PROVIDER

Thursday, 18 February 2010


<!DOCTYPE html>
<html>
<head>
<title>OpenID Example</title>
<link rel=”openid.server” href=”http://openid.example.com/
op/” />
<link rel=”openid2.provider” href=”http://openid.example.com/
op/” />
</head>
<body>
<h1>Hi, I’m Mike Malone. I think Ruby rules!</h2>
</body>
</html>

2. RELYING PARTY discovers OPENID


PROVIDER

Thursday, 18 February 2010


<!DOCTYPE html>
<html>
<head>
<title>OpenID Example</title>
<link rel=”openid.server” href=”http://openid.example.com/
op/” />
<link rel=”openid2.provider” href=”http://openid.example.com/
op/” />
</head>
<body>
<h1>Hi, I’m Mike Malone. I think Ruby rules!</h2>
</body>
</html>

2. RELYING PARTY discovers OPENID


PROVIDER

Thursday, 18 February 2010


Yadis Discovery

• A protocol for discovering services


associated with a Yadis ID, which is a URL
or and XRI i-name.
• Result of discovery is an XRDS Capabilities
document.

Thursday, 18 February 2010


You have 3 options
1. Using the custom HTTP response header
X-XRDS-Location

2. Using the HTML meta tag <meta http-


equiv=”X-XRDS-Location” ...>
3. Using content negotiation in HTTP
requests with Accept: application/
xrds+xml

Thursday, 18 February 2010


HTTP Header
$ curl -I -H "Accept: application/xrds+xml" www.yahoo.com
HTTP/1.1 200 OK
Date: Mon, 15 Feb 2010 17:09:24 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM..."
Vary: Accept,Accept-Encoding
Cache-Control: private
X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
Content-Type: text/html; charset=utf-8
Age: 0
Connection: keep-alive
Server: YTS/1.17.23.1

Thursday, 18 February 2010


HTTP Header
$ curl -I -H "Accept: application/xrds+xml" www.yahoo.com
HTTP/1.1 200 OK
Date: Mon, 15 Feb 2010 17:09:24 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM..."
Vary: Accept,Accept-Encoding
Cache-Control: private
X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
Content-Type: text/html; charset=utf-8
Age: 0
Connection: keep-alive
Server: YTS/1.17.23.1

Thursday, 18 February 2010


HTTP Header
$ curl -I -H "Accept: application/xrds+xml" www.yahoo.com
HTTP/1.1 200 OK
Date: Mon, 15 Feb 2010 17:09:24 GMT
P3P: policyref="http://info.yahoo.com/w3c/p3p.xml", CP="CAO DSP COR CUR ADM..."
Vary: Accept,Accept-Encoding
Cache-Control: private
X-XRDS-Location: http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
Content-Type: text/html; charset=utf-8
Age: 0
Connection: keep-alive
Server: YTS/1.17.23.1

Thursday, 18 February 2010


$ curl http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0"
xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://specs.openid.net/extensions/pape/1.0</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/extensions/sreg/1.1</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<Type>http://specs.openid.net/extensions/oauth/1.0</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/lang-pref</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
<Type>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/
privatepersonalidentifier</Type>
<Type>http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf</Type>
<Type>http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf</
Type>
<Type>http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf</Type>
<URI>https://open.login.yahooapis.com/openid/op/auth</URI>
</Service>
</XRD>
</xrds:XRDS>

Thursday, 18 February 2010


$ curl http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS xmlns:xrds="xri://$xrds" xmlns:openid="http://openid.net/xmlns/1.0"
xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<Type>http://specs.openid.net/extensions/pape/1.0</Type>
<Type>http://openid.net/sreg/1.0</Type>
<Type>http://openid.net/extensions/sreg/1.1</Type>
<Type>http://openid.net/srv/ax/1.0</Type>
<Type>http://specs.openid.net/extensions/oauth/1.0</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/lang-pref</Type>
<Type>http://specs.openid.net/extensions/ui/1.0/mode/popup</Type>
<Type>http://schemas.xmlsoap.org/ws/2005/05/identity/claims/
privatepersonalidentifier</Type>
<Type>http://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf</Type>
<Type>http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf</
Type>
<Type>http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdf</Type>
<URI>https://open.login.yahooapis.com/openid/op/auth</URI>
</Service>
</XRD>
</xrds:XRDS>

Thursday, 18 February 2010


3. RELYING PARTY redirects USER to
OPENID PROVIDER

Thursday, 18 February 2010


3.OPENID PROVIDER redirects USER to
RELYING PARTY to prove claim
Thursday, 18 February 2010
Problems with OpenID

• Lots of providers and not a lot of


consumers. Companies want to “own”
OpenID.
• I don’t normally go by “http://stu.mp” on
the internets.
• Fails the “mom test.”

Thursday, 18 February 2010


Implementing a Relying Party

Thursday, 18 February 2010


return render_to_response('login.html', {
'error': error
}, RequestContext(request))

1. USER enters their OpenID into oid/


consumer/views.py

Thursday, 18 February 2010


{% extends 'base.html' %}

{% block body %}
<h1>OpenID Example</h1>
{% if error %}
<p>Error: {{ error }}</p>
{% endif %}
<form method="post" action=".">
<label for="openid_identifier">Login with OpenID:</label>
<input type="text" id="openid_identifier" name="openid_identifier"/>
<input type="submit" />
</form>
{% endblock %}

1. USER enters their OpenID into oid/


consumer/views.py

Thursday, 18 February 2010


{% extends 'base.html' %}

{% block body %}
<h1>OpenID Example</h1>
{% if error %}
<p>Error: {{ error }}</p>
{% endif %}
<form method="post" action=".">
<label for="openid_identifier">Login with OpenID:</label>
<input type="text" id="openid_identifier" name="openid_identifier"/>
<input type="submit" />
</form>
{% endblock %}

1. USER enters their OpenID into oid/


consumer/views.py

Thursday, 18 February 2010


def login(request):
"""
Start the OpenID authentication process.
"""
if request.POST:
# Start OpenID authentication.
openid_url = request.POST.get('openid_identifier', '')
auth_consumer = get_consumer(request)

try:
auth_request = auth_consumer.begin(openid_url)
except DiscoveryFailure, ex:
# Some protocol-level failure occurred.
error = "OpenID discovery error: %s" % (ex,)
return HttpResponseRedirect('%s?%s' % (reverse('openid_login'),
urllib.urlencode({'error': error})))

2. RELYING PARTY discovers OPENID PROVIDER


using Yadis in oid/consumer/views.py

Thursday, 18 February 2010


def login(request):
"""
Start the OpenID authentication process.
"""
if request.POST:
# Start OpenID authentication.
openid_url = request.POST.get('openid_identifier', '')
auth_consumer = get_consumer(request)

try:
auth_request = auth_consumer.begin(openid_url)
except DiscoveryFailure, ex:
# Some protocol-level failure occurred.
error = "OpenID discovery error: %s" % (ex,)
return HttpResponseRedirect('%s?%s' % (reverse('openid_login'),
urllib.urlencode({'error': error})))

2. RELYING PARTY discovers OPENID PROVIDER


using Yadis in oid/consumer/views.py

Thursday, 18 February 2010


def login(request):
"""
Start the OpenID authentication process.
"""
if request.POST:
# Start OpenID authentication.
openid_url = request.POST.get('openid_identifier', '')
auth_consumer = get_consumer(request)

try:
auth_request = auth_consumer.begin(openid_url)
except DiscoveryFailure, ex:
# Some protocol-level failure occurred.
error = "OpenID discovery error: %s" % (ex,)
return HttpResponseRedirect('%s?%s' % (reverse('openid_login'),
urllib.urlencode({'error': error})))

2. RELYING PARTY discovers OPENID PROVIDER


using Yadis in oid/consumer/views.py

Thursday, 18 February 2010


def get_consumer(request):
"""
Get a Consumer object to perform OpenID authentication.
"""
return consumer.Consumer(request.session, DjangoOpenIDStore())

a. Instantiate Consumer with user data-store and


global data-store in oid/consumer/views.py

Thursday, 18 February 2010


def get_consumer(request):
"""
Get a Consumer object to perform OpenID authentication.
"""
return consumer.Consumer(request.session, DjangoOpenIDStore())

a. Instantiate Consumer with user data-store and


global data-store in oid/consumer/views.py

Thursday, 18 February 2010


response = yadisDiscover(uri)

yadis_url = response.normalized_uri
body = response.response_text
try:
openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body)
except XRDSError:
# Does not parse as a Yadis XRDS file
openid_services = []

if not openid_services:
# Either not an XRDS or there are no OpenID services.
if response.isXRDS():
# if we got the Yadis content-type or followed the Yadis
# header, re-fetch the document without following the Yadis
# header, with no Accept header.
return discoverNoYadis(uri)

# Try to parse the response as HTML.


# <link rel="...">
openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)
return (yadis_url, getOPOrUserServices(openid_services))

b. Discover OPENID PROVIDER endpoint


in oid/consumer/discover.py
Thursday, 18 February 2010
response = yadisDiscover(uri)

yadis_url = response.normalized_uri
body = response.response_text
try:
openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body)
except XRDSError:
# Does not parse as a Yadis XRDS file
openid_services = []

if not openid_services:
# Either not an XRDS or there are no OpenID services.
if response.isXRDS():
# if we got the Yadis content-type or followed the Yadis
# header, re-fetch the document without following the Yadis
# header, with no Accept header.
return discoverNoYadis(uri)

# Try to parse the response as HTML.


# <link rel="...">
openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)
return (yadis_url, getOPOrUserServices(openid_services))

b. Discover OPENID PROVIDER endpoint


in oid/consumer/discover.py
Thursday, 18 February 2010
response = yadisDiscover(uri)

yadis_url = response.normalized_uri
body = response.response_text
try:
openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body)
except XRDSError:
# Does not parse as a Yadis XRDS file
openid_services = []

if not openid_services:
# Either not an XRDS or there are no OpenID services.
if response.isXRDS():
# if we got the Yadis content-type or followed the Yadis
# header, re-fetch the document without following the Yadis
# header, with no Accept header.
return discoverNoYadis(uri)

# Try to parse the response as HTML.


# <link rel="...">
openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)
return (yadis_url, getOPOrUserServices(openid_services))

b. Discover OPENID PROVIDER endpoint


in oid/consumer/discover.py
Thursday, 18 February 2010
response = yadisDiscover(uri)

yadis_url = response.normalized_uri
body = response.response_text
try:
openid_services = OpenIDServiceEndpoint.fromXRDS(yadis_url, body)
except XRDSError:
# Does not parse as a Yadis XRDS file
openid_services = []

if not openid_services:
# Either not an XRDS or there are no OpenID services.
if response.isXRDS():
# if we got the Yadis content-type or followed the Yadis
# header, re-fetch the document without following the Yadis
# header, with no Accept header.
return discoverNoYadis(uri)

# Try to parse the response as HTML.


# <link rel="...">
openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)
return (yadis_url, getOPOrUserServices(openid_services))

b. Discover OPENID PROVIDER endpoint


in oid/consumer/discover.py
Thursday, 18 February 2010
if request.is_secure():
realm = 'https://%s' % (request.get_host(),)
else:
realm = 'http://%s' % (request.get_host(),)
return_to = request.build_absolute_uri(reverse('openid_finish'))
return HttpResponseRedirect(auth_request.redirectURL(realm, return_to))

3. RELYING PARTY redirects to OPENID


PROVIDER in oid/consumer/views.py

Thursday, 18 February 2010


if request.is_secure():
realm = 'https://%s' % (request.get_host(),)
else:
realm = 'http://%s' % (request.get_host(),)
return_to = request.build_absolute_uri(reverse('openid_finish'))
return HttpResponseRedirect(auth_request.redirectURL(realm, return_to))

3. RELYING PARTY redirects to OPENID


PROVIDER in oid/consumer/views.py

Thursday, 18 February 2010


What’s that OpenID
Realm?
• A pattern that represents the part of the URL-space for
which an OpenID authentication request is valid
• Presented by the provider to the end user during the
authentication request
• Gives the user an indication of the scope of the
authentication request
• The openid.return_to URL must match the realm
• URL scheme and port must match
• URL path must be equal or a subdirectory of realm

Thursday, 18 February 2010


OpenID Realm

Thursday, 18 February 2010


Realm URL Match

http://example.com/ http://example.com/foo

http://example.com/ https://example.com/op/

http://example.com/ http://snarf.example.com/baz/

http://*.example.com/ http://snarf.example.com/baz/

http://example.com:8080/ http://example.com/

http://*.com/ http://example.com/

Thursday, 18 February 2010


def finish(request):
auth_consumer = get_consumer(request)
return_to = request.build_absolute_uri(reverse('openid_finish'))
response = auth_consumer.complete(request.REQUEST, return_to)
context = {
consumer.CANCEL: {
'error': 'OpenID authentication was cancelled.',
},
consumer.FAILURE: {
'error': 'OpenID authentication failed.',
},
consumer.SUCCESS: {
'identity': response.getDisplayIdentifier(),
}
}.get(response.status, {'error': 'Unknown response type.'})
return render_to_response('finish.html',context,RequestContext(request))

4. OPENID PROVIDER redirects RELYING PARTY


which verifies claim in oid/consumer/views.py

Thursday, 18 February 2010


There are two ways RELYING PARTY can veryify
authentication with an OPENID PROVIDER

1. Use a pre-established association with the


OpenID provider to check the cryptographic
signature of the response.
2. Contact the OpenID provider directly to
verify the response.

Thursday, 18 February 2010


Questions?!

Thursday, 18 February 2010


Implementing an OpenID Provider

Thursday, 18 February 2010


1. Decode the response and determine its nature.
2. Decide how to respond to the request.
a. The server can respond automatically to some
request types by passing to
server.handleRequest.

b. For checkid_setup and


checkid_immediate you should decide
whether the user is authorized to claim the
identity in question
3. Encode the response according to the OpenID
protocol

Thursday, 18 February 2010


def get_server(request):
return Server(DjangoOpenIDStore(),
request.build_absolute_uri(reverse('openid_provider')))

def openid_provider(request, identity=None):


server = get_server(request)
try:
openid_request = server.decodeRequest(request.REQUEST)
except ProtocolError, ex:
return render_to_response('provider/index.html', {
'error': str(ex),
}, RequestContext(request))

1. Decode request and and determine its


nature in oid/provider/views.py

Thursday, 18 February 2010


def get_server(request):
return Server(DjangoOpenIDStore(),
request.build_absolute_uri(reverse('openid_provider')))

def openid_provider(request, identity=None):


server = get_server(request)
try:
openid_request = server.decodeRequest(request.REQUEST)
except ProtocolError, ex:
return render_to_response('provider/index.html', {
'error': str(ex),
}, RequestContext(request))

1. Decode request and and determine its


nature in oid/provider/views.py

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Do some stuff.
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)

2. Decide how to respond to the reqeust


in oid/provider/views.py

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Do some stuff.
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)

2. Decide how to respond to the reqeust


in oid/provider/views.py

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Got a checkid request. Always return yes. In a real server
# we'd check that the user is logged in and ask them if they
# trust the relying party, etc.
if openid_request.idSelect():
# If an identity URL wasn't entered at the RP, then we have to
# come up with one. We’ll ask the user who they want to be.
if 'identity' in request.POST:
identity = reverse('openid_identity', kwargs={
'identity': request.POST['identity']
})
response = openid_request.answer(True,
identity=request.build_absolute_uri(identity))
else:
return render_to_response('provider/index.html', {
'trust_root': openid_request.trust_root,
'needs_identity': True,
}, RequestContext(request))
else:
response = openid_request.answer(True,
identity=openid_request.identity)

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Got a checkid request. Always return yes. In a real server
# we'd check that the user is logged in and ask them if they
# trust the relying party, etc.
if openid_request.idSelect():
# If an identity URL wasn't entered at the RP, then we have to
# come up with one. We’ll ask the user who they want to be.
if 'identity' in request.POST:
identity = reverse('openid_identity', kwargs={
'identity': request.POST['identity']
})
response = openid_request.answer(True,
identity=request.build_absolute_uri(identity))
else:
return render_to_response('provider/index.html', {
'trust_root': openid_request.trust_root,
'needs_identity': True,
}, RequestContext(request))
else:
response = openid_request.answer(True,
identity=openid_request.identity)

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Got a checkid request. Always return yes. In a real server
# we'd check that the user is logged in and ask them if they
# trust the relying party, etc.
if openid_request.idSelect():
# If an identity URL wasn't entered at the RP, then we have to
# come up with one. We’ll ask the user who they want to be.
if 'identity' in request.POST:
identity = reverse('openid_identity', kwargs={
'identity': request.POST['identity']
})
response = openid_request.answer(True,
identity=request.build_absolute_uri(identity))
else:
return render_to_response('provider/index.html', {
'trust_root': openid_request.trust_root,
'needs_identity': True,
}, RequestContext(request))
else:
response = openid_request.answer(True,
identity=openid_request.identity)

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Got a checkid request. Always return yes. In a real server
# we'd check that the user is logged in and ask them if they
# trust the relying party, etc.
if openid_request.idSelect():
# If an identity URL wasn't entered at the RP, then we have to
# come up with one. We’ll ask the user who they want to be.
if 'identity' in request.POST:
identity = reverse('openid_identity', kwargs={
'identity': request.POST['identity']
})
response = openid_request.answer(True,
identity=request.build_absolute_uri(identity))
else:
return render_to_response('provider/index.html', {
'trust_root': openid_request.trust_root,
'needs_identity': True,
}, RequestContext(request))
else:
response = openid_request.answer(True,
identity=openid_request.identity)

Thursday, 18 February 2010


if openid_request.mode in ('checkid_immediate', 'checkid_setup'):
# Got a checkid request. Always return yes. In a real server
# we'd check that the user is logged in and ask them if they
# trust the relying party, etc.
if openid_request.idSelect():
# If an identity URL wasn't entered at the RP, then we have to
# come up with one. We’ll ask the user who they want to be.
if 'identity' in request.POST:
identity = reverse('openid_identity', kwargs={
'identity': request.POST['identity']
})
response = openid_request.answer(True,
identity=request.build_absolute_uri(identity))
else:
return render_to_response('provider/index.html', {
'trust_root': openid_request.trust_root,
'needs_identity': True,
}, RequestContext(request))
else:
response = openid_request.answer(True,
identity=openid_request.identity)

Thursday, 18 February 2010


def render_openid_response(openid_response):
response = HttpResponse(openid_response.body,
status=openid_response.code)
for header, value in openid_response.headers.iteritems():
response[header] = value
return response

...

response = openid_request.answer(True,
identity=openid_request.identity)
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)
try:
return render_openid_response(server.encodeResponse(response))
except EncodingError, ex:
return render_to_response('provider/index.html', {
'error': cgi.escape(ex.response.encodeToKVForm()),
}, RequestContext(request))

3. Encode the response according to the OpenID


protocol in oid/provider/views.py

Thursday, 18 February 2010


def render_openid_response(openid_response):
response = HttpResponse(openid_response.body,
status=openid_response.code)
for header, value in openid_response.headers.iteritems():
response[header] = value
return response

...

response = openid_request.answer(True,
identity=openid_request.identity)
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)
try:
return render_openid_response(server.encodeResponse(response))
except EncodingError, ex:
return render_to_response('provider/index.html', {
'error': cgi.escape(ex.response.encodeToKVForm()),
}, RequestContext(request))

3. Encode the response according to the OpenID


protocol in oid/provider/views.py

Thursday, 18 February 2010


def render_openid_response(openid_response):
response = HttpResponse(openid_response.body,
status=openid_response.code)
for header, value in openid_response.headers.iteritems():
response[header] = value
return response

...

response = openid_request.answer(True,
identity=openid_request.identity)
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)
try:
return render_openid_response(server.encodeResponse(response))
except EncodingError, ex:
return render_to_response('provider/index.html', {
'error': cgi.escape(ex.response.encodeToKVForm()),
}, RequestContext(request))

3. Encode the response according to the OpenID


protocol in oid/provider/views.py

Thursday, 18 February 2010


def render_openid_response(openid_response):
response = HttpResponse(openid_response.body,
status=openid_response.code)
for header, value in openid_response.headers.iteritems():
response[header] = value
return response

...

response = openid_request.answer(True,
identity=openid_request.identity)
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)
try:
return render_openid_response(server.encodeResponse(response))
except EncodingError, ex:
return render_to_response('provider/index.html', {
'error': cgi.escape(ex.response.encodeToKVForm()),
}, RequestContext(request))

3. Encode the response according to the OpenID


protocol in oid/provider/views.py

Thursday, 18 February 2010


def render_openid_response(openid_response):
response = HttpResponse(openid_response.body,
status=openid_response.code)
for header, value in openid_response.headers.iteritems():
response[header] = value
return response

...

response = openid_request.answer(True,
identity=openid_request.identity)
else:
# Got some other kind of request. Let the server take care of it.
response = server.handleRequest(openid_request)
try:
return render_openid_response(server.encodeResponse(response))
except EncodingError, ex:
return render_to_response('provider/index.html', {
'error': cgi.escape(ex.response.encodeToKVForm()),
}, RequestContext(request))

3. Encode the response according to the OpenID


protocol in oid/provider/views.py

Thursday, 18 February 2010


Wrapping things up
with Yadis
1. Put an XRDS capabilities document on
your server that points to your OpenID
Provider
2. Set the X-XRDS-Location header for
requests to your OpenID Provider (e.g.,
the root of your site) pointing to your
XRDS capabilities document

Thursday, 18 February 2010


Minimal XRDS
Document
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
xmlns:xrds="xri://$xrds"
xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<URI>http://127.0.0.1:8000/op/</URI>
</Service>
</XRD>
</xrds:XRDS>

Thursday, 18 February 2010


Minimal XRDS
Document
<?xml version="1.0" encoding="UTF-8"?>
<xrds:XRDS
xmlns:xrds="xri://$xrds"
xmlns="xri://$xrd*($v*2.0)">
<XRD>
<Service priority="0">
<Type>http://specs.openid.net/auth/2.0/server</Type>
<URI>http://127.0.0.1:8000/op/</URI>
</Service>
</XRD>
</xrds:XRDS>

Thursday, 18 February 2010


Questions?!

Thursday, 18 February 2010


BREAK!

Thursday, 18 February 2010


Protocol for secure, password-free
API authorization. Your valet key
for the web.

Thursday, 18 February 2010


OAuth was conceived by Blaine Cook, the guy
who wrote Twitter, as a standard way to
delegate authorization to a third party. It was
based on Flickr’s API, though there’s lots of prior
art in this space.

Thursday, 18 February 2010


Problems Addressed
• Allows a user to share private resources
with a third party without having to give out
their credentials.
• Allows APIs to keep track of third parties
accessing private resources.
• Allows users to revoke access to third
parties.

Thursday, 18 February 2010


Why do I care?
OAuth is the standard interface for YOS,
OpenSocial, GData, MySpace, and (soon)
Facebook’s API and Facebook Connect.
Twitter is aggressively moving people from
the password anti-pattern to OAuth as
well.

Thursday, 18 February 2010


The Password Anti-
Pattern
1. Exposes your password to third-parties.
2. Forces third-parties to store your username and
password in plain text.
3. Provides no mechanism for revoking
permission.
4. No support for other authentication mechanisms
(e.g., OpenID, two-factor authentication)
5. Teaches users to be phished

Thursday, 18 February 2010


Terminology
• A CLIENT (consumer) is a library or
application capable of making OAuth
requests wishing to access protected
resources.
• A SERVER (provider) is an HTTP server
capable of receiving and authenticating
OAuth requests sent by the client.

Thursday, 18 February 2010


Terminology

• A PROTECTED RESOURCE is a restricted


resource or operation that can be accessed
via the server by the client.
• A RESOURCE OWNER is the owner of a
protected resource who can access the
server and delegate access to a client.

Thursday, 18 February 2010


Terminology
• CREDENTIALS are comprised of a unique key
and a shared secret.
• CLIENT CREDENTIALS identify an individual
client.
• TEMPORARY CREDENTIALS identify an
authorization request from a client.
• TOKEN CREDENTIALS uniquely identify a client
accessing the server on behalf of a resource
owner.

Thursday, 18 February 2010


Terminology in
Transition
Old New
Consumer Client
Service Provider Server
User Resource Owner
Consumer Key & Secret Client Credentials
Request Token & Secret Temporary Credentials
Access Token & Secret Token Credentials

Thursday, 18 February 2010


How’s it work?
Three-legged OAuth

• A 3rd party sends a signed request to the


API for a request token.
• 3rd party redirects user to API provider
asking for access.
• User grants access and is redirected back to
3rd party.
• 3rd party sends a signed request to the API
to obtain authorized access tokens.

Thursday, 18 February 2010


1.CLIENT requests access from USER

Thursday, 18 February 2010


GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
GET /oauth/request_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="0fevohyb",
oauth_callback="http%3A%2F%2Fwefollow.com%2Fadd%2Ftags",
oauth_signature="74KNZJeDHnMBp0EMJ9ZHt%2FXKycU%3D"

HTTP 1.1 200 OK


Content-Type: application/x-www-form-urlencoded

oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=
25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

2. CLIENT requests a TEMPORARY TOKEN


from SERVER
Thursday, 18 February 2010
3. USER is redirected to SERVER to grant
access to CLIENT
Thursday, 18 February 2010
HTTP/1.1 302 Redirect
Location: http://wefollow.com/add/tags?
oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_veri
fier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

4. SERVER redirects to USER back to


CLIENTʼs oauth_callback

Thursday, 18 February 2010


HTTP/1.1 302 Redirect
Location: http://wefollow.com/add/tags?
oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_veri
fier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

4. SERVER redirects to USER back to


CLIENTʼs oauth_callback

Thursday, 18 February 2010


HTTP/1.1 302 Redirect
Location: http://wefollow.com/add/tags?
oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_veri
fier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

4. SERVER redirects to USER back to


CLIENTʼs oauth_callback

Thursday, 18 February 2010


HTTP/1.1 302 Redirect
Location: http://wefollow.com/add/tags?
oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_veri
fier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

4. SERVER redirects to USER back to


CLIENTʼs oauth_callback

Thursday, 18 February 2010


HTTP/1.1 302 Redirect
Location: http://wefollow.com/add/tags?
oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_veri
fier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

4. SERVER redirects to USER back to


CLIENTʼs oauth_callback

Thursday, 18 February 2010


POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
POST /oauth/access_token HTTP/1.1
Host: twitter.com
Authorization: OAuth realm="wefollow",
oauth_consumer_key="qqoivmh7sy8ils15",
oauth_token="dn8gxxob06rirxzp30e68dsffkd08hfh",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1266278609",
oauth_nonce="1gfwpizc",
oauth_verifier="5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro",
oauth_signature="gKgrFCywp7rO0OXSjdot%2FIHF7IU%3D"

HTTP/1.1 200 OK
Content-Type: application/x-www-form-urlencoded

oauth_token=bjunknhae01qkvxw3litbej17w6h74w1&oauth_token_secret=hx
ffqqtx4vxitjex5evacz6kmplw3ms7

4. CLIENT exchanges TEMPORARY


CREDENTIALS for TOKEN CREDENTIALS
Thursday, 18 February 2010
OAUTH AUTHENTICATION FLOW
Person Using Web Browser / Manual Entry
Consumer / Service Provider v1.0a
Consumer Service Provider Consumer Requests Consumer Requests
A E

Obtain Unauthorized
Request Token Access Token

Request Token
Request includes Request includes
Request Grant
A oauth_consumer_key oauth_consumer_key
Request Token Request Token
oauth_signature_method oauth_token
oauth_signature oauth_signature_method
oauth_timestamp oauth_signature
oauth_nonce oauth_timestamp
oauth_version (optional) oauth_nonce
Direct User to oauth_callback oauth_version (optional)
B
Service Provider oauth_verifier

User Authorizes
Request Token
Obtain User
C
Authorization
Service Provider Service Provider
B F
Grants Request Token Grants Access Token
Direct User to Response includes Response includes
Consumer oauth_token oauth_token
oauth_token_secret oauth_token_secret
Exchange Request Token oauth_callback_confirmed
Request
D for Access Token
Access Token
Consumer Directs User to Consumer Accesses
C G
Grant Service Provider Protected Resources
E
Access Token
Request includes Request includes
oauth_token (optional) oauth_consumer_key
F oauth_token
Service Provider Directs oauth_signature_method
D oauth_signature
User to Consumer
oauth_timestamp
Access Protected
Request includes oauth_nonce
Resources
oauth_token oauth_version (optional)
G oauth_verifier

http://s3.pixane.com/Oauth_diagram.pdf

Thursday, 18 February 2010


How’s it work?
Two-legged OAuth

• A 3rd party is given a key and a shared


secret by the API provider.
• 3rd party signs the request using a standard
algorithm defined by OAuth.
• API re-signs the request in the same
manner using the known shared secret.

Thursday, 18 February 2010


Problems with OAuth
• No way to delegate tokens (e.g. Allowing TwitPic
to post on behalf of Tweetie).
• No way to programmatically register clients.
• HTTP body is not signed.
• User experience from non-web applications is
clunky.
• Endpoints are specific to an API and server.
• No standard for specifying permissions.

Thursday, 18 February 2010


Implementing an OAuth Client

Thursday, 18 February 2010


client = oauth.Client(consumer)
url = 'https://twitter.com/oauth/request_token?%s' % (urllib.urlencode({
'oauth_callback': request.build_absolute_uri(reverse('callback')),
}))

resp, content = client.request(url, 'GET')

if resp['status'] != '200':
raise Exception('Invalid response: %s.' % (resp['status'],))

temporary_credentials = dict(urlparse.parse_qsl(content))
request.session[TEMP_CREDENTIALS_KEY] = temporary_credentials

1. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER

Thursday, 18 February 2010


client = oauth.Client(consumer)
url = 'https://twitter.com/oauth/request_token?%s' % (urllib.urlencode({
'oauth_callback': request.build_absolute_uri(reverse('callback')),
}))

resp, content = client.request(url, 'GET')

if resp['status'] != '200':
raise Exception('Invalid response: %s.' % (resp['status'],))

temporary_credentials = dict(urlparse.parse_qsl(content))
request.session[TEMP_CREDENTIALS_KEY] = temporary_credentials

1. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER

Thursday, 18 February 2010


client = oauth.Client(consumer)
url = 'https://twitter.com/oauth/request_token?%s' % (urllib.urlencode({
'oauth_callback': request.build_absolute_uri(reverse('callback')),
}))

resp, content = client.request(url, 'GET')

if resp['status'] != '200':
raise Exception('Invalid response: %s.' % (resp['status'],))

temporary_credentials = dict(urlparse.parse_qsl(content))
request.session[TEMP_CREDENTIALS_KEY] = temporary_credentials

1. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER

Thursday, 18 February 2010


auth_url = 'https://twitter.com/oauth/authorize?%s' % (urllib.urlencode({
'oauth_token': temporary_credentials['oauth_token'],
}))
return HttpResponseRedirect(auth_url)

1. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER
Thursday, 18 February 2010
auth_url = 'https://twitter.com/oauth/authorize?%s' % (urllib.urlencode({
'oauth_token': temporary_credentials['oauth_token'],
}))
return HttpResponseRedirect(auth_url)

1. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER
Thursday, 18 February 2010
auth_url = 'https://twitter.com/oauth/authorize?%s' % (urllib.urlencode({
'oauth_token': temporary_credentials['oauth_token'],
}))
return HttpResponseRedirect(auth_url)

2. CLIENT requests TEMPORARY


CREDENTIALS from the SERVER
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
def callback(request):
temp_credentials = request.session.get(TEMP_CREDENTIALS_KEY, None)
if temp_credentials is None:
raise Exception('No temporary credentials.')

temp_credentials = oauth.Token(temp_credentials['oauth_token'],
temp_credentials['oauth_token_secret'])
consumer = oauth.Consumer(CLIENT_KEY, CLIENT_SECRET)
client = oauth.Client(consumer, temp_credentials)

url = 'https://twitter.com/oauth/access_token?%s' % (urllib.urlencode({


'oauth_verifier': request.GET.get('oauth_verifier', '')
}))
resp, content = client.request(url, 'POST')

access_token = dict(urlparse.parse_qsl(content))
request.session[TOKEN_CREDENTIALS_KEY] = access_token
return HttpResponseRedirect(reverse('consumer'))

3. USER redirected back CLIENTʼs oauth_callback


which exchanges TEMPORARY CREDENTIALS
for TOKEN CREDENTIALS
Thursday, 18 February 2010
try:
credentials = oauth.Token(credentials['oauth_token'],
credentials['oauth_token_secret'])
client = oauth.Client(consumer, credentials)
resp, content = client.request(
'http://twitter.com/account/verify_credentials.json',
'GET'
)
data = json.loads(content)
return HttpResponse('Logged in as %s' % (data['screen_name'],))
except:
del request.session[TOKEN_CREDENTIALS_KEY]
raise

CLIENT can now make OAuth requests to


SERVER on behalf of USER using TOKEN
CREDENTIALS

Thursday, 18 February 2010


try:
credentials = oauth.Token(credentials['oauth_token'],
credentials['oauth_token_secret'])
client = oauth.Client(consumer, credentials)
resp, content = client.request(
'http://twitter.com/account/verify_credentials.json',
'GET'
)
data = json.loads(content)
return HttpResponse('Logged in as %s' % (data['screen_name'],))
except:
del request.session[TOKEN_CREDENTIALS_KEY]
raise

CLIENT can now make OAuth requests to


SERVER on behalf of USER using TOKEN
CREDENTIALS

Thursday, 18 February 2010


try:
credentials = oauth.Token(credentials['oauth_token'],
credentials['oauth_token_secret'])
client = oauth.Client(consumer, credentials)
resp, content = client.request(
'http://twitter.com/account/verify_credentials.json',
'GET'
)
data = json.loads(content)
return HttpResponse('Logged in as %s' % (data['screen_name'],))
except:
del request.session[TOKEN_CREDENTIALS_KEY]
raise

CLIENT can now make OAuth requests to


SERVER on behalf of USER using TOKEN
CREDENTIALS

Thursday, 18 February 2010


Questions?!

Thursday, 18 February 2010


Implementing an OAuth Server

Thursday, 18 February 2010


oauth_request = oauth.Request.from_request(
request.method,
request.build_absolute_uri(),
headers=headers,
parameters=dict(parameters),
query_string=request.environ.get('QUERY_STRING', '')
)

try:
consumer = models.Consumer.objects.get(pk=oauth_request['oauth_consumer_key’])
except (KeyError, models.Consumer.DoesNotExist):
raise HttpUnauthorized('Consumer key missing or invalid.')
server = oauth.Server()
server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
...
oauth_token = oauth.Token(token.key, token.secret)
...
try:
oauth_consumer = oauth.Consumer(consumer.key, consumer.secret)
server.verify_request(oauth_request, oauth_consumer, oauth_token)
except ValueError, ex:
return HttpResponseUnauthorized(str(ex))

3. Validate the OAuth request in djoauth/


oauth_provider/views.py

Thursday, 18 February 2010


oauth_request = oauth.Request.from_request(
request.method,
request.build_absolute_uri(),
headers=headers,
parameters=dict(parameters),
query_string=request.environ.get('QUERY_STRING', '')
)

try:
consumer = models.Consumer.objects.get(pk=oauth_request['oauth_consumer_key’])
except (KeyError, models.Consumer.DoesNotExist):
raise HttpUnauthorized('Consumer key missing or invalid.')
server = oauth.Server()
server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
...
oauth_token = oauth.Token(token.key, token.secret)
...
try:
oauth_consumer = oauth.Consumer(consumer.key, consumer.secret)
server.verify_request(oauth_request, oauth_consumer, oauth_token)
except ValueError, ex:
return HttpResponseUnauthorized(str(ex))

3. Validate the OAuth request in djoauth/


oauth_provider/views.py

Thursday, 18 February 2010


oauth_request = oauth.Request.from_request(
request.method,
request.build_absolute_uri(),
headers=headers,
parameters=dict(parameters),
query_string=request.environ.get('QUERY_STRING', '')
)

try:
consumer = models.Consumer.objects.get(pk=oauth_request['oauth_consumer_key’])
except (KeyError, models.Consumer.DoesNotExist):
raise HttpUnauthorized('Consumer key missing or invalid.')
server = oauth.Server()
server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
...
oauth_token = oauth.Token(token.key, token.secret)
...
try:
oauth_consumer = oauth.Consumer(consumer.key, consumer.secret)
server.verify_request(oauth_request, oauth_consumer, oauth_token)
except ValueError, ex:
return HttpResponseUnauthorized(str(ex))

3. Validate the OAuth request in djoauth/


oauth_provider/views.py

Thursday, 18 February 2010


oauth_request = oauth.Request.from_request(
request.method,
request.build_absolute_uri(),
headers=headers,
parameters=dict(parameters),
query_string=request.environ.get('QUERY_STRING', '')
)

try:
consumer = models.Consumer.objects.get(pk=oauth_request['oauth_consumer_key’])
except (KeyError, models.Consumer.DoesNotExist):
raise HttpUnauthorized('Consumer key missing or invalid.')
server = oauth.Server()
server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
...
oauth_token = oauth.Token(token.key, token.secret)
...
try:
oauth_consumer = oauth.Consumer(consumer.key, consumer.secret)
server.verify_request(oauth_request, oauth_consumer, oauth_token)
except ValueError, ex:
return HttpResponseUnauthorized(str(ex))

3. Validate the OAuth request in djoauth/


oauth_provider/views.py

Thursday, 18 February 2010


oauth_request = oauth.Request.from_request(
request.method,
request.build_absolute_uri(),
headers=headers,
parameters=dict(parameters),
query_string=request.environ.get('QUERY_STRING', '')
)

try:
consumer = models.Consumer.objects.get(pk=oauth_request['oauth_consumer_key’])
except (KeyError, models.Consumer.DoesNotExist):
raise HttpUnauthorized('Consumer key missing or invalid.')
server = oauth.Server()
server.add_signature_method(oauth.SignatureMethod_HMAC_SHA1())
...
oauth_token = oauth.Token(token.key, token.secret)
...
try:
oauth_consumer = oauth.Consumer(consumer.key, consumer.secret)
server.verify_request(oauth_request, oauth_consumer, oauth_token)
except ValueError, ex:
return HttpResponseUnauthorized(str(ex))

1. Validate the OAuth request in djoauth/


oauth_provider/views.py

Thursday, 18 February 2010


token = models.Token(consumer=request.consumer,
type=models.Token.TYPE_TEMPORARY)
try:
token.callback = request.oauth_request['oauth_callback']
except KeyError:
return HttpResponse('Missing oauth_callback', status=400)
token.consumer = request.consumer
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
'oauth_callback_confirmed': True,
}
return HttpResponse(urllib.urlencode(data))

2. Handle request for TEMPORARY


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


token = models.Token(consumer=request.consumer,
type=models.Token.TYPE_TEMPORARY)
try:
token.callback = request.oauth_request['oauth_callback']
except KeyError:
return HttpResponse('Missing oauth_callback', status=400)
token.consumer = request.consumer
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
'oauth_callback_confirmed': True,
}
return HttpResponse(urllib.urlencode(data))

2. Handle request for TEMPORARY


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


@login_required
def authorize(request):
try:
token = models.Token.objects.get(pk=request.GET['oauth_token'])
except (KeyError, models.Token.DoesNotExist):
raise Http404()
if not token.type == token.TYPE_TEMPORARY:
raise Http404()
if request.method == 'GET':
return render_to_response('authorize.html', {
'consumer': token.consumer,
}, RequestContext(request))
elif request.method == 'POST':
token.approved = True
token.user = request.user
token.verifier = models.make_random_key()
token.save()
callback = parameterize_url(token.callback, {
'oauth_token': token.key,
'oauth_verifier': token.verifier,
})
return HttpResponseRedirect(callback)
else:
return HttpResponseNotAllowed(['GET', 'POST'])

3. USER authorizes CLIENT in


djoauth/oauth_provider/views.py
Thursday, 18 February 2010
@login_required
def authorize(request):
try:
token = models.Token.objects.get(pk=request.GET['oauth_token'])
except (KeyError, models.Token.DoesNotExist):
raise Http404()
if not token.type == token.TYPE_TEMPORARY:
raise Http404()
if request.method == 'GET':
return render_to_response('authorize.html', {
'consumer': token.consumer,
}, RequestContext(request))
elif request.method == 'POST':
token.approved = True
token.user = request.user
token.verifier = models.make_random_key()
token.save()
callback = parameterize_url(token.callback, {
'oauth_token': token.key,
'oauth_verifier': token.verifier,
})
return HttpResponseRedirect(callback)
else:
return HttpResponseNotAllowed(['GET', 'POST'])

3. USER authorizes CLIENT in


djoauth/oauth_provider/views.py
Thursday, 18 February 2010
@login_required
def authorize(request):
try:
token = models.Token.objects.get(pk=request.GET['oauth_token'])
except (KeyError, models.Token.DoesNotExist):
raise Http404()
if not token.type == token.TYPE_TEMPORARY:
raise Http404()
if request.method == 'GET':
return render_to_response('authorize.html', {
'consumer': token.consumer,
}, RequestContext(request))
elif request.method == 'POST':
token.approved = True
token.user = request.user
token.verifier = models.make_random_key()
token.save()
callback = parameterize_url(token.callback, {
'oauth_token': token.key,
'oauth_verifier': token.verifier,
})
return HttpResponseRedirect(callback)
else:
return HttpResponseNotAllowed(['GET', 'POST'])

3. USER authorizes CLIENT in


djoauth/oauth_provider/views.py
Thursday, 18 February 2010
def token_credentials(request):
if not request.token.approved:
return HttpResponseUnauthorized('Temporary token not authorized.')
if request.token.verifier != request.oauth_request.get('oauth_verifier', None):
return HttpResponseUnauthorized('Missing or invalid OAuth verifier.')
token = models.Token(
consumer=request.consumer,
type=models.Token.TYPE_TOKEN,
user=request.token.user,
approved=True
)
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
}
request.token.delete() # temporary tokens can only be redeemed once.
return HttpResponse(urllib.urlencode(data))

4. SERVER handles request for TOKEN


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


def token_credentials(request):
if not request.token.approved:
return HttpResponseUnauthorized('Temporary token not authorized.')
if request.token.verifier != request.oauth_request.get('oauth_verifier', None):
return HttpResponseUnauthorized('Missing or invalid OAuth verifier.')
token = models.Token(
consumer=request.consumer,
type=models.Token.TYPE_TOKEN,
user=request.token.user,
approved=True
)
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
}
request.token.delete() # temporary tokens can only be redeemed once.
return HttpResponse(urllib.urlencode(data))

4. SERVER handles request for TOKEN


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


def token_credentials(request):
if not request.token.approved:
return HttpResponseUnauthorized('Temporary token not authorized.')
if request.token.verifier != request.oauth_request.get('oauth_verifier', None):
return HttpResponseUnauthorized('Missing or invalid OAuth verifier.')
token = models.Token(
consumer=request.consumer,
type=models.Token.TYPE_TOKEN,
user=request.token.user,
approved=True
)
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
}
request.token.delete() # temporary tokens can only be redeemed once.
return HttpResponse(urllib.urlencode(data))

4. SERVER handles request for TOKEN


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


def token_credentials(request):
if not request.token.approved:
return HttpResponseUnauthorized('Temporary token not authorized.')
if request.token.verifier != request.oauth_request.get('oauth_verifier', None):
return HttpResponseUnauthorized('Missing or invalid OAuth verifier.')
token = models.Token(
consumer=request.consumer,
type=models.Token.TYPE_TOKEN,
user=request.token.user,
approved=True
)
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
}
request.token.delete() # temporary tokens can only be redeemed once.
return HttpResponse(urllib.urlencode(data))

4. SERVER handles request for TOKEN


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


def token_credentials(request):
if not request.token.approved:
return HttpResponseUnauthorized('Temporary token not authorized.')
if request.token.verifier != request.oauth_request.get('oauth_verifier', None):
return HttpResponseUnauthorized('Missing or invalid OAuth verifier.')
token = models.Token(
consumer=request.consumer,
type=models.Token.TYPE_TOKEN,
user=request.token.user,
approved=True
)
token.save()
data = {
'oauth_token': token.key,
'oauth_token_secret': token.secret,
}
request.token.delete() # temporary tokens can only be redeemed once.
return HttpResponse(urllib.urlencode(data))

4. SERVER handles request for TOKEN


CREDENTIALS in djoauth/
oauth_provider/views.py

Thursday, 18 February 2010


@oauth_request()
def protected_resource(request):
if request.user is None:
raise HttpResponseUnauthorized()
return HttpResponse(request.user.username)

SERVER can now authenticate OAuth requests


from CLIENT on behalf of USER using TOKEN
CREDENTIALS

Thursday, 18 February 2010


Questions?!

Thursday, 18 February 2010


Relationships

Thursday, 18 February 2010


Portable Contacts
A common access pattern and
contact schema for providing
access to address books and friend
lists.

Thursday, 18 February 2010


• Language and platform-neutral protocol for
consumers to request address book, profile, and
friends list information from service providers.

• Defines an XML and JSON serialization of vCard.


• Simple RESTful API for sharing, filtering, and
searching contacts between social web sites.
• Protected by OAuth or HTTP Basic auth.
• Discovery uses XRDS-Simple (Yadis) like
OpenID.
• Part of the OpenSocial stack, which is supported
by Google, MySpace, etc.
• Integrated with OpenID and OAuth in Gmail.

Thursday, 18 February 2010


http://www-opensocial.googleusercontent.com/api/people/@me/@all

{
"startIndex": 0,
"totalResults": 3,
"entry": [
{
"profileUrl": "http://www.google.com/s2/profiles/user1ID",
"isViewer": true,
"id": "user1ID",
"thumbnailUrl": "http://www.google.com/s2/photos/private/photo1ID",
"name": {
"formatted": "Elizabeth Bennet",
"familyName": "Bennet",
"givenName": "Elizabeth"
},
"displayName": "Elizabeth Bennet",
...
},
...
}

Thursday, 18 February 2010


# setup an OAuth client, as described before
client = get_google_oauth_client()

response, content = client.request(


"http://www-opensocial.googleusercontent.com/api/people/@me/@all", "GET"
)
contacts = json.loads(content)
for contact in contacts["entries"]:
print contact["id"], contact["displayName"]

Thursday, 18 February 2010


Required Endpoints

/@me/@all Return all contact information

Return contact information for the


/@me/@all/{id}
provided ID, if such a contact exists

Return contact information for the


/@me/@self
authenticated user

Thursday, 18 February 2010


Filtering
The field name to filter by (e.g.
filterBy
displayName)

The comparison operation for filtering.


filterOp Legal values are “equals”, “contains”,
“startswith”, and “present.”

filterValue The value to filter by.

Thursday, 18 February 2010


Sorting

sortBy The name of the field to use for sorting.

The order to sort by. Can be “ascending”


sortOrder
or “descending.”

Thursday, 18 February 2010


Pagination
The offset of the first contact to be
startIndex returned.

The maximum number of contacts the


count
client would like the provider to return.

Thursday, 18 February 2010


Presentation
A comma-separated list of fields the
fields
client would like returned.

The format the provider should return.


format
Can be “xml” or “json.”

Thursday, 18 February 2010


Problems Addressed
• A common access pattern and contact
schema.
• Minimal complexity with the lightest
possible tool chain requirements.
• Well specified authentication and access
rules.
• Avoids the password anti-pattern.
Thursday, 18 February 2010
Content+Activity

Thursday, 18 February 2010


Activity Streams

An extension to Atom to
standardize activity streams & news
feeds

Thursday, 18 February 2010


Standard serialization for “lifestreaming”
activities such as Facebook’s news feed,
FriendFeed’s feed, Jaiku blog posts, Last.fm
music streams, etc.

Thursday, 18 February 2010


Activity Streams goal is to express activities
in such a way that they can be usefully
interpreted with zero knowledge of the
originating service.

Thursday, 18 February 2010


Why do I care?
Supported by Google, MySpace, Facebook,
and others.

Thursday, 18 February 2010


Also ....

Thursday, 18 February 2010


Thursday, 18 February 2010
Anatomy of an Activity

Thursday, 18 February 2010


Actor verb object [context]

Thursday, 18 February 2010


Joe favorited a photo [on flickr.com]

Thursday, 18 February 2010


Actor verb object {target} [context]

Thursday, 18 February 2010


Joe favorited a photo {of Mike} [on
flickr.com]

Thursday, 18 February 2010


• Verbs and Object Types are defined by IRIs
• There is a standard set, but you can also
create your own

Thursday, 18 February 2010


<entry>
<id>tag:photopanic.example.com,2008:activity01</id>
<title>Geraldine posted a Photo on PhotoPanic</title>
<published>2008-11-02T15:29:00Z</published>
<link rel="alternate" type="text/html"
href="/geraldine/activities/1" />
<activity:verb>
http://activitystrea.ms/schema/1.0/post
</activity:verb>
<activity:object>
<id>tag:photopanic.example.com,2008:photo01</id>
<title>My Cat</title>
<published>2008-11-02T15:29:00Z</published>
<link rel="alternate" type="text/html"
href="/geraldine/photos/1" />
<activity:object-type>
tag:atomactivity.example.com,2008:photo
</activity:object-type>
<source>
<title>Geraldine's Photos</title>
<link rel="self" type="application/atom+xml"
href="/geraldine/photofeed.xml" />
<link rel="alternate" type="text/html"
href="/geraldine/" />
</source>
</activity:object>
<content type="html">
&lt;p&gt;Geraldine posted a Photo on PhotoPanic&lt;/p&gt;
&lt;img src="/geraldine/photo1.jpg"&gt;
</content>
</entry>

Thursday, 18 February 2010


<entry>
<id>tag:photopanic.example.com,2008:activity01</id>
<title>Geraldine posted a Photo on PhotoPanic</title>
<published>2008-11-02T15:29:00Z</published>
<link rel="alternate" type="text/html"
href="/geraldine/activities/1" />
<activity:verb>
http://activitystrea.ms/schema/1.0/post
</activity:verb>
<activity:object>
<id>tag:photopanic.example.com,2008:photo01</id>
<title>My Cat</title>
<published>2008-11-02T15:29:00Z</published>
<link rel="alternate" type="text/html"
href="/geraldine/photos/1" />
<activity:object-type>
tag:atomactivity.example.com,2008:photo
</activity:object-type>
<source>
<title>Geraldine's Photos</title>
<link rel="self" type="application/atom+xml"
href="/geraldine/photofeed.xml" />
<link rel="alternate" type="text/html"
href="/geraldine/" />
</source>
</activity:object>
<content type="html">
&lt;p&gt;Geraldine posted a Photo on PhotoPanic&lt;/p&gt;
&lt;img src="/geraldine/photo1.jpg"&gt;
</content>
</entry>

Thursday, 18 February 2010


JSON in the works!

Thursday, 18 February 2010


oEmbed

Thursday, 18 February 2010


• A format for allowing an embedded
representation of a URL on third party
sites
• Allows a website to display embedded
content (e.g., a photo or video) when a
user posts a link to that resource without
parsing HTML or using proprietary APIs

Thursday, 18 February 2010


Why do I care?
oEmbed is currently supported by
YouTube, Flickr,Viddler, Qik, Revision3,
Hulu,Vimeo, and many others.

Thursday, 18 February 2010


http://www.flickr.com/services/oembed/?url=http%3A//www.flickr.com/photos/bees/
2341623661/

{
"version": "1.0",
"type": "photo",
"width": 240,
"height": 160,
"title": "ZB8T0193",
"url": "http://farm4.static.flickr.com/3123/2341623661_7c99f48bbf_m.jpg",
"author_name": "Bees",
"author_url": "http://www.flickr.com/photos/bees/",
"provider_name": "Flickr",
"provider_url": "http://www.flickr.com/"
}

Thursday, 18 February 2010


url The URL you’re wishing to embed.

The maximum width of the embed. This


maxwidth
only applies to certain types.

The maximum width of the embed. This


maxheight
only applies to certain types.

Desired format of the response. Can be


format
either “json” or “xml.”

Thursday, 18 February 2010


type Resource type (e.g. “photo”)
version oEmbed version (1.0)
title Test title describing resource
author_name Author of resource
author_url URL for author of resource
provider_name Name of resource provider
provider_url URL of resource provider
cache_age Suggested cache lifetime in seconds
thumbnail_url URL to a thumbnail image
thumbnail_width Thumbnail width
thumbnail_height Thumbnail height
Thursday, 18 February 2010
url Source URL of a photo embed

height Height in pixels of the image or embed

width Width in pixels of the image or embed

The HTML required to embed the


html
resource.

Thursday, 18 February 2010


Disovery
<link rel="alternate" type="application/json+oembed"
href="http://flickr.com/services/oembed?url=http%3A//
flickr.com/photos/bees/2362225867/&format=json"
title="Bacon Lollys oEmbed Profile" />
<link rel="alternate" type="text/xml+oembed"
href="http://flickr.com/services/oembed?url=http%3A//
flickr.com/photos/bees/2362225867/&format=xml"
title="Bacon Lollys oEmbed Profile" />

Thursday, 18 February 2010


Disovery
<link rel="alternate" type="application/json+oembed"
href="http://flickr.com/services/oembed?url=http%3A//
flickr.com/photos/bees/2362225867/&format=json"
title="Bacon Lollys oEmbed Profile" />
<link rel="alternate" type="text/xml+oembed"
href="http://flickr.com/services/oembed?url=http%3A//
flickr.com/photos/bees/2362225867/&format=xml"
title="Bacon Lollys oEmbed Profile" />

Thursday, 18 February 2010


To avoid XSS vulnerabilities you should serve
embeds in an iframe hosted on another domain.

Thursday, 18 February 2010


>>> import html5lib                                                           
>>> import urllib
>>> import json
>>>
>>> f = urllib.urlopen('http://www.youtube.com/watch?v=A0Gs4xGw1Eg')
>>> doc = html5lib.parse(f, treebuilder='lxml')
>>>
>>> head = doc.getroot().find('{http://www.w3.org/1999/xhtml}head')
>>> for link in head.findall('{http://www.w3.org/1999/xhtml}link
[@rel="alternate"]'):
...     if link.attrib.get('type') == 'application/json+oembed':
...         oembed_url = link.attrib['href']
...         oembed = json.load(urllib.urlopen(oembed_url))
...         print oembed
...
{u'provider_url': u'http://www.youtube.com/', u'version': u'1.0', u'title':
u'Radio Friendly Song', u'author_name': u'jonlajoie', u'height': 295,
u'width': 480, u'html': u'<object width="480" height="295"><param
name="movie" value="http://www.youtube.com/v/A0Gs4xGw1Eg&fs=1"></
param><param name="allowFullScreen" value="true"></param><param
name="allowscriptaccess" value="always"></param><embed src="http://
www.youtube.com/v/A0Gs4xGw1Eg&fs=1" type="application/x-shockwave-flash"
width="480" height="295" allowscriptaccess="always"
allowfullscreen="true"></embed></object>', u'author_url': u'http://
www.youtube.com/user/jonlajoie', u'provider_name': u'YouTube', u'type':
u'video'}

Thursday, 18 February 2010


>>> import html5lib                                                           
>>> import urllib
>>> import json
>>>
>>> f = urllib.urlopen('http://www.youtube.com/watch?v=A0Gs4xGw1Eg')
>>> doc = html5lib.parse(f, treebuilder='lxml')
>>>
>>> head = doc.getroot().find('{http://www.w3.org/1999/xhtml}head')
>>> for link in head.findall('{http://www.w3.org/1999/xhtml}link
[@rel="alternate"]'):
...     if link.attrib.get('type') == 'application/json+oembed':
...         oembed_url = link.attrib['href']
...         oembed = json.load(urllib.urlopen(oembed_url))
...         print oembed
...
{u'provider_url': u'http://www.youtube.com/', u'version': u'1.0', u'title':
u'Radio Friendly Song', u'author_name': u'jonlajoie', u'height': 295,
u'width': 480, u'html': u'<object width="480" height="295"><param
name="movie" value="http://www.youtube.com/v/A0Gs4xGw1Eg&fs=1"></
param><param name="allowFullScreen" value="true"></param><param
name="allowscriptaccess" value="always"></param><embed src="http://
www.youtube.com/v/A0Gs4xGw1Eg&fs=1" type="application/x-shockwave-flash"
width="480" height="295" allowscriptaccess="always"
allowfullscreen="true"></embed></object>', u'author_url': u'http://
www.youtube.com/user/jonlajoie', u'provider_name': u'YouTube', u'type':
u'video'}

Thursday, 18 February 2010


Thanks!

Thursday, 18 February 2010


@joestump

Thursday, 18 February 2010


@mjmalone
Thursday, 18 February 2010

Anda mungkin juga menyukai