social plumbing pycon 2010

Upload: mike-maccana

Post on 30-May-2018

216 views

Category:

Documents


0 download

TRANSCRIPT

  • 8/14/2019 Social Plumbing PyCon 2010

    1/216

    Open Stack: Hacking theSocial Web with Python

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    2/216

    Who are you?

    Joe Stump Mike Malone

    @mjmalone@joestump

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    3/216

    A special thanks to ...

    David Recordon

    @daveman692

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    4/216

    Reduce HTTP Requests

    Bundle JavaScript and CSSUse sprites for imagesReduce images / outside objects

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    5/216

    Introductions

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    6/216

    Open data is

    increasingly importantas services move

    online. Tim Oreilly (OSCON, 2007)

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    7/216

    65% of the worldwideinternet audience

    visited at least one

    social network lastmonth. comScore

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    8/216

    Rules of Web 2.0

    1. The perpetual beta becomes a process forengaging customers.

    2. Share and share-alike data, reusing others andproviding 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

  • 8/14/2019 Social Plumbing PyCon 2010

    9/216

    Rules of Web 2.0

    1. The perpetual beta becomes a process forengaging customers.

    2. Share and share-alike data, reusing others andproviding 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

  • 8/14/2019 Social Plumbing PyCon 2010

    10/216

    www

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    11/216

    Web 2.0

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    12/216

    ?

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    13/216

    c:\

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    14/216

    http://

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    15/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    16/216

    http://

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    17/216

    http://xkcd.com/256/

    Thursday, 18 February 2010

    http://blogs.forrester.com/groundswell/2008/03/the-future-of-s.htmlhttp://blogs.forrester.com/groundswell/2008/03/the-future-of-s.html
  • 8/14/2019 Social Plumbing PyCon 2010

    18/216

    Social networks willbe like air. Charlene Li, Forrester

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

    Thursday, 18 February 2010

    http://blogs.forrester.com/groundswell/2008/03/the-future-of-s.htmlhttp://blogs.forrester.com/groundswell/2008/03/the-future-of-s.html
  • 8/14/2019 Social Plumbing PyCon 2010

    19/216

    Each API had its own mechanisms forauthentication

    Each API had its own data formats

    Few, if any, APIs had the ability to delegateaccess to third parties

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    20/216

    http://

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    21/216

    I have anidea!

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    22/216

    Identity Relationships Content+Activity

    Profiles

    Accounts

    Friends

    FollowersColleagues

    Photos

    BlogsPokes

    Favorites

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    23/216

    Identity Relationships Activity

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    24/216

    The open stackis a set of technologies that work togetherto democratize and decentralize key pieces of the social

    web.Open stack technologies allow users to share their identity,relationships, activity, and content across applicationsand platforms.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    25/216

    Great! How the helldoes it work?

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    26/216

    Cooperation

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    27/216

    The nice thing aboutstandards is that you

    have so many to

    choose from. Andrew S. Tanenbaum

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    28/216

    OAuth OpenID WebFinger

    FOAF XFN Portable Contacts

    LRDD XRD-Simple Yadis

    microformats oEmbed SalmonAtom Activity Streams PubSubHubBub

    DiSo oExchange JSON

    RDF RSS XML

    XMPP CSS

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    29/216

    The open stack for the socialweb is built entirely on HTTP.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    30/216

    HTTP urllib, urllib2, httplib, httplib2

    JSON simplejson, json-py, cjson

    XMLElementTree, xml.dom, xml.dom.minidom,

    xml.sax, lxml, simplexml

    Atom FeedParser

    HTML BeautifulSoup, html5lib

    URI cgi, urllib, urlparse

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    31/216

    CryptographicSignatures for Dummies

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    32/216

    Alice wants to make sure a message, deliveredover an insecure channel, came from Bob. Bob

    and Alice get together and come up with ashared secret. They randomly select

    crocodile as their shared secret.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    33/216

    >>> 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

  • 8/14/2019 Social Plumbing PyCon 2010

    34/216

    >>> 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

  • 8/14/2019 Social Plumbing PyCon 2010

    35/216

    >>> 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

  • 8/14/2019 Social Plumbing PyCon 2010

    36/216

    >>> 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

  • 8/14/2019 Social Plumbing PyCon 2010

    37/216

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

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    38/216

    >>> message, hash = received_message.split('&')

    >>> print messageHi, Alice. You rock!>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()>>> assert hash == check_hash>>>

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    39/216

    >>> message, hash = received_message.split('&')

    >>> print messageHi, Alice. You rock!>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()>>> assert hash == check_hash>>>

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    40/216

    >>> message, hash = received_message.split('&')>>> print message

    'Hi, Alice. You stink!'>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()>>> assert hash == check_hashTraceback (most recent call last):File "", line 1, in

    AssertionError>>>

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    41/216

    >>> message, hash = received_message.split('&')>>> print message

    'Hi, Alice. You stink!'>>> check_hash = hmac.new(key, message, hashlib.sha1).hexdigest()>>> assert hash == check_hashTraceback (most recent call last):File "", line 1, in

    AssertionError>>>

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    42/216

    Identity

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    43/216

    A decentralized mechanism

    for single sign-on

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    44/216

    OpenID was created by Brad Fitzpatrick and

    David Recordon while working at Six Apart.Their goal was to create a decentralizedauthentication mechanism for blog comments.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    45/216

    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 theOpenID provider supports it.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    46/216

    Why do I care?

    Well over 500,000,000 accounts fromAOL, Google, Yahoo!, MySpace, Facebook,

    and others are OpenID enabled.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    47/216

    Terminology

    An IDENTIFIER is an HTTP URI or an XRI.

    A RELYING PARTY (RP) as an applicationwishing to identify a user or proveownership of an identity.

    An OPENID PROVIDER (OP) is a serverthat the RP relies on to assert identity of agiven user.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    48/216

    Hows it work?

    An OpenID is just a URL. My OpenID ishttp://stu.mp.

    You claim an OpenID (e.g. login orregister) with relying party.

    The relying party relies on the providerto authenticate the user.

    Thursday, 18 February 2010

    http://stu.mp/http://stu.mp/
  • 8/14/2019 Social Plumbing PyCon 2010

    49/216

    1. USER identifies themselves to a RELYING

    PARTY using their OpenID

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    50/216

    1. USER identifies themselves to a RELYING

    PARTY using their OpenID

    http://stu.mp

    Thursday, 18 February 2010

    http://stu.mp/http://stu.mp/
  • 8/14/2019 Social Plumbing PyCon 2010

    51/216

    OpenID Example

    Hi, Im Mike Malone. I think Ruby rules!

    2. RELYING PARTY discovers OPENIDPROVIDER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    52/216

    OpenID Example

    Hi, Im Mike Malone. I think Ruby rules!

    2. RELYING PARTY discovers OPENIDPROVIDER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    53/216

    OpenID Example

    Hi, Im Mike Malone. I think Ruby rules!

    2. RELYING PARTY discovers OPENIDPROVIDER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    54/216

    OpenID Example

    Hi, Im Mike Malone. I think Ruby rules!

    2. RELYING PARTY discovers OPENIDPROVIDER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    55/216

    Yadis Discovery

    A protocol for discovering services

    associated with a Yadis ID, which is a URLor and XRI i-name.

    Result of discovery is an XRDS Capabilitiesdocument.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    56/216

    You have 3 options

    1. Using the custom HTTP response headerX-XRDS-Location

    2. Using the HTML meta tag

    3. Using content negotiation in HTTPrequests with Accept: application/

    xrds+xml

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    57/216

    $ curl -I -H "Accept: application/xrds+xml" www.yahoo.com

    HTTP/1.1 200 OKDate: 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: 0Connection: keep-alive

    Server: YTS/1.17.23.1

    HTTP Header

    Thursday, 18 February 2010

    http://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://info.yahoo.com/w3c/p3p.xmlhttp://info.yahoo.com/w3c/p3p.xmlhttp://www.yahoo.com/http://www.yahoo.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    58/216

    $ curl -I -H "Accept: application/xrds+xml" www.yahoo.com

    HTTP/1.1 200 OKDate: 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: 0Connection: keep-alive

    Server: YTS/1.17.23.1

    HTTP Header

    Thursday, 18 February 2010

    http://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://info.yahoo.com/w3c/p3p.xmlhttp://info.yahoo.com/w3c/p3p.xmlhttp://www.yahoo.com/http://www.yahoo.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    59/216

    $ curl -I -H "Accept: application/xrds+xml" www.yahoo.com

    HTTP/1.1 200 OKDate: 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: 0Connection: keep-alive

    Server: YTS/1.17.23.1

    HTTP Header

    Thursday, 18 February 2010

    http://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://info.yahoo.com/w3c/p3p.xmlhttp://info.yahoo.com/w3c/p3p.xmlhttp://www.yahoo.com/http://www.yahoo.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    60/216

    $ curl http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds

    http://specs.openid.net/auth/2.0/server

    http://specs.openid.net/extensions/pape/1.0http://openid.net/sreg/1.0

    http://openid.net/extensions/sreg/1.1http://openid.net/srv/ax/1.0

    http://specs.openid.net/extensions/oauth/1.0 http://specs.openid.net/extensions/ui/1.0/lang-pref

    http://specs.openid.net/extensions/ui/1.0/mode/popuphttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/

    privatepersonalidentifierhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf

    http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf

    http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttps://open.login.yahooapis.com/openid/op/auth

    Thursday, 18 February 2010

    https://open.login.yahooapis.com/openid/op/authhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/oauth/1.0http://openid.net/srv/ax/1.0http://openid.net/sreg/1.0http://specs.openid.net/extensions/pape/1.0https://open.login.yahooapis.com/openid/op/authhttps://open.login.yahooapis.com/openid/op/authhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/oauth/1.0http://specs.openid.net/extensions/oauth/1.0http://openid.net/srv/ax/1.0http://openid.net/srv/ax/1.0http://openid.net/extensions/sreg/1.1http://openid.net/extensions/sreg/1.1http://openid.net/sreg/1.0http://openid.net/sreg/1.0http://specs.openid.net/extensions/pape/1.0http://specs.openid.net/extensions/pape/1.0http://specs.openid.net/auth/2.0/serverhttp://specs.openid.net/auth/2.0/serverhttp://openid.net/xmlns/1.0http://openid.net/xmlns/1.0http://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
  • 8/14/2019 Social Plumbing PyCon 2010

    61/216

    $ curl http://open.login.yahooapis.com/openid20/www.yahoo.com/xrds

    http://specs.openid.net/auth/2.0/server

    http://specs.openid.net/extensions/pape/1.0http://openid.net/sreg/1.0

    http://openid.net/extensions/sreg/1.1http://openid.net/srv/ax/1.0

    http://specs.openid.net/extensions/oauth/1.0 http://specs.openid.net/extensions/ui/1.0/lang-pref

    http://specs.openid.net/extensions/ui/1.0/mode/popuphttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/

    privatepersonalidentifierhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdf

    http://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdf

    http://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttps://open.login.yahooapis.com/openid/op/auth

    Thursday, 18 February 2010

    https://open.login.yahooapis.com/openid/op/authhttps://open.login.yahooapis.com/openid/op/authhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/oauth/1.0http://openid.net/srv/ax/1.0http://openid.net/sreg/1.0http://specs.openid.net/extensions/pape/1.0https://open.login.yahooapis.com/openid/op/authhttps://open.login.yahooapis.com/openid/op/authhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://csrc.nist.gov/publications/nistpubs/800-63/SP800-63V1_0_2.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/openid-trust-level1.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://www.idmanagement.gov/schema/2009/05/icam/no-pii.pdfhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://schemas.xmlsoap.org/ws/2005/05/identity/claims/privatepersonalidentifierhttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/mode/popuphttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/ui/1.0/lang-prefhttp://specs.openid.net/extensions/oauth/1.0http://specs.openid.net/extensions/oauth/1.0http://openid.net/srv/ax/1.0http://openid.net/srv/ax/1.0http://openid.net/extensions/sreg/1.1http://openid.net/extensions/sreg/1.1http://openid.net/sreg/1.0http://openid.net/sreg/1.0http://specs.openid.net/extensions/pape/1.0http://specs.openid.net/extensions/pape/1.0http://specs.openid.net/auth/2.0/serverhttp://specs.openid.net/auth/2.0/serverhttp://openid.net/xmlns/1.0http://openid.net/xmlns/1.0http://open.login.yahooapis.com/openid20/www.yahoo.com/xrdshttp://open.login.yahooapis.com/openid20/www.yahoo.com/xrds
  • 8/14/2019 Social Plumbing PyCon 2010

    62/216

    3. RELYING PARTY redirects USER to

    OPENID PROVIDER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    63/216

    3.OPENID PROVIDER redirects USER to

    RELYING PARTY to prove claim

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    64/216

    Problems with OpenID

    Lots of providers and not a lot ofconsumers. Companies want to ownOpenID.

    I dont normally go by http://stu.mp onthe internets.

    Fails the mom test.

    Thursday, 18 February 2010

    http://stu.mp/http://stu.mp/
  • 8/14/2019 Social Plumbing PyCon 2010

    65/216

    Implementing a Relying Party

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    66/216

  • 8/14/2019 Social Plumbing PyCon 2010

    67/216

    {% extends 'base.html' %}

    {% block body %}

    OpenID Example

    {% if error %}

    Error: {{ error }}

    {% endif %}

    Login with OpenID:

    {% endblock %}

    1. USER enters their OpenID into oid/

    consumer/views.py

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    68/216

    {% extends 'base.html' %}

    {% block body %}

    OpenID Example

    {% if error %}

    Error: {{ error }}

    {% endif %}

    Login with OpenID:

    {% endblock %}

    1. USER enters their OpenID into oid/

    consumer/views.py

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    69/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    70/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    71/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    72/216

    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 andglobal data-store in oid/consumer/views.py

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    73/216

    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 andglobal data-store in oid/consumer/views.py

    Thursday, 18 February 2010

    response = yadisDiscover(uri)

  • 8/14/2019 Social Plumbing PyCon 2010

    74/216

    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.

    #

    openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)

    return (yadis_url, getOPOrUserServices(openid_services))

    b.Discover OPENID PROVIDER endpoint

    in oid/consumer/discover.pyThursday, 18 February 2010

    response = yadisDiscover(uri)

  • 8/14/2019 Social Plumbing PyCon 2010

    75/216

    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.

    #

    openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)

    return (yadis_url, getOPOrUserServices(openid_services))

    b.Discover OPENID PROVIDER endpoint

    in oid/consumer/discover.pyThursday, 18 February 2010

    response = yadisDiscover(uri)

    di l li d i

  • 8/14/2019 Social Plumbing PyCon 2010

    76/216

    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.

    #

    openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)

    return (yadis_url, getOPOrUserServices(openid_services))

    b.Discover OPENID PROVIDER endpoint

    in oid/consumer/discover.pyThursday, 18 February 2010

    response = yadisDiscover(uri)

    di l li d i

  • 8/14/2019 Social Plumbing PyCon 2010

    77/216

    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.

    #

    openid_services = OpenIDServiceEndpoint.fromHTML(yadis_url, body)

    return (yadis_url, getOPOrUserServices(openid_services))

    b.Discover OPENID PROVIDER endpoint

    in oid/consumer/discover.pyThursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    78/216

    3. RELYING PARTY redirects to OPENID

    PROVIDER in oid/consumer/views.py

    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))

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    79/216

    3. RELYING PARTY redirects to OPENID

    PROVIDER in oid/consumer/views.py

    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))

    Thursday, 18 February 2010

    Whats that OpenID

  • 8/14/2019 Social Plumbing PyCon 2010

    80/216

    What s that OpenID

    Realm? A pattern that represents the part of the URL-space forwhich an OpenID authentication request is valid

    Presented by the provider to the end user during theauthentication request

    Gives the user an indication of the scope of theauthentication 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

  • 8/14/2019 Social Plumbing PyCon 2010

    81/216

    OpenID Realm

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    82/216

    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

    http://example.com/http://example.com/http://example.com/http://example.com/http://example.com:8080/http://example.com:8080/http://snarf.example.com/baz/http://snarf.example.com/baz/http://snarf.example.com/bazhttp://snarf.example.com/bazhttp://example.com/http://example.com/https://example.com/op/https://example.com/op/http://example.com/http://example.com/http://exam/http://exam/http://example.com/http://example.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    83/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    84/216

    There are two ways RELYING PARTY can veryifyauthentication with an OPENID PROVIDER

    1.Use a pre-established association with the

    OpenID provider to check the cryptographicsignature of the response.

    2.Contact the OpenID provider directly toverify the response.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    85/216

    Questions?!

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    86/216

    Implementing an OpenID Provider

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    87/216

    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 question3. Encode the response according to the OpenID

    protocol

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    88/216

    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 itsnature in oid/provider/views.py

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    89/216

    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 itsnature in oid/provider/views.py

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    90/216

    2. Decide how to respond to the reqeust

    in oid/provider/views.py

    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)

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    91/216

    2. Decide how to respond to the reqeust

    in oid/provider/views.py

    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)

    Thursday, 18 February 2010

    if openid request mode in ('checkid immediate' 'checkid setup'):

  • 8/14/2019 Social Plumbing PyCon 2010

    92/216

    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. Well 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'):

  • 8/14/2019 Social Plumbing PyCon 2010

    93/216

    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. Well 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'):

  • 8/14/2019 Social Plumbing PyCon 2010

    94/216

    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. Well 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'):

  • 8/14/2019 Social Plumbing PyCon 2010

    95/216

    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. Well 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'):

  • 8/14/2019 Social Plumbing PyCon 2010

    96/216

    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. Well 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,

    st t s openid response code)

  • 8/14/2019 Social Plumbing PyCon 2010

    97/216

    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)

  • 8/14/2019 Social Plumbing PyCon 2010

    98/216

    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)

  • 8/14/2019 Social Plumbing PyCon 2010

    99/216

    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)

  • 8/14/2019 Social Plumbing PyCon 2010

    100/216

    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)

  • 8/14/2019 Social Plumbing PyCon 2010

    101/216

    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

  • 8/14/2019 Social Plumbing PyCon 2010

    102/216

    Wrapping things up

    with Yadis1. Put an XRDS capabilities document on

    your server that points to your OpenIDProvider

    2. Set the X-XRDS-Location header forrequests to your OpenID Provider (e.g.,the root of your site) pointing to yourXRDS capabilities document

    Thursday, 18 February 2010

    Minimal XRDS

  • 8/14/2019 Social Plumbing PyCon 2010

    103/216

    Minimal XRDS

    Documenthttp://specs.openid.net/auth/2.0/serverhttp://127.0.0.1:8000/op/

    Thursday, 18 February 2010

    Minimal XRDS

    http://127.0.0.1:8000/op/http://127.0.0.1:8000/op/http://specs.openid.net/auth/2.0/serverhttp://specs.openid.net/auth/2.0/server
  • 8/14/2019 Social Plumbing PyCon 2010

    104/216

    Minimal XRDS

    Documenthttp://specs.openid.net/auth/2.0/serverhttp://127.0.0.1:8000/op/

    Thursday, 18 February 2010

    http://127.0.0.1:8000/op/http://127.0.0.1:8000/op/http://specs.openid.net/auth/2.0/serverhttp://specs.openid.net/auth/2.0/server
  • 8/14/2019 Social Plumbing PyCon 2010

    105/216

    Questions?!

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    106/216

    BREAK!

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    107/216

    Protocol for secure, password-freeAPI authorization. Your valet key

    for the web.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    108/216

    OAuth was conceived by Blaine Cook, the guywho wrote Twitter, as a standard way to

    delegate authorization to a third party. It wasbased on Flickrs API, though theres lots of priorart in this space.

    Thursday, 18 February 2010

    P bl Add d

  • 8/14/2019 Social Plumbing PyCon 2010

    109/216

    Problems Addressed

    Allows a user to share private resourceswith a third party withouthaving to give out

    their credentials.

    Allows APIs to keep track of third partiesaccessing private resources.

    Allows users to revoke access to thirdparties.

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    110/216

    Why do I care?OAuth is the standard interface for YOS,

    OpenSocial, GData, MySpace, and (soon)Facebooks API and Facebook Connect.

    Twitter is aggressively moving people fromthe password anti-pattern to OAuth as

    well.

    Thursday, 18 February 2010

    The Password Anti-

  • 8/14/2019 Social Plumbing PyCon 2010

    111/216

    Pattern1. 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

  • 8/14/2019 Social Plumbing PyCon 2010

    112/216

    Terminology

    A CLIENT (consumer) is a library orapplication capable of making OAuth

    requests wishing to access protectedresources.

    A SERVER (provider) is an HTTP servercapable of receiving and authenticatingOAuth requests sent by the client.

    Thursday, 18 February 2010

    Terminology

  • 8/14/2019 Social Plumbing PyCon 2010

    113/216

    Terminology

    A PROTECTED RESOURCE is a restrictedresource or operation that can be accessedvia the server by the client.

    A RESOURCE OWNER is the owner of aprotected resource who can access the

    server and delegate access to a client.

    Thursday, 18 February 2010

    Terminology

  • 8/14/2019 Social Plumbing PyCon 2010

    114/216

    Terminology

    CREDENTIALS are comprised of a unique keyand a shared secret.

    CLIENTCREDENTIALS identify an individualclient.

    TEMPORARYCREDENTIALS identify anauthorization request from a client.

    TOKENCREDENTIALS uniquely identify a clientaccessing the server on behalf of a resourceowner.

    Thursday, 18 February 2010

    Terminology in

  • 8/14/2019 Social Plumbing PyCon 2010

    115/216

    gy

    TransitionOld New

    Consumer Client

    Service Provider Server

    User Resource Owner

    Consumer Key & Secret Client Credentials

    Request Token & Secret Temporary CredentialsAccess Token & Secret Token Credentials

    Thursday, 18 February 2010

    Hows it work?

  • 8/14/2019 Social Plumbing PyCon 2010

    116/216

    How s it work?

    A 3rd party sends a signed request to theAPI for a request token.

    3rd party redirects user to API providerasking for access. User grants access and is redirected back to

    3rd party.

    3rd party sends a signed request to the APIto obtain authorized access tokens.

    Three-legged OAuth

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    117/216

    1.CLIENT requests access from USER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1i

  • 8/14/2019 Social Plumbing PyCon 2010

    118/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1H t t itt

  • 8/14/2019 Social Plumbing PyCon 2010

    119/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1H t t itt

  • 8/14/2019 Social Plumbing PyCon 2010

    120/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1H t t itt

  • 8/14/2019 Social Plumbing PyCon 2010

    121/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1H t t itt

  • 8/14/2019 Social Plumbing PyCon 2010

    122/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

    GET /oauth/request_token HTTP/1.1Host: t itter com

  • 8/14/2019 Social Plumbing PyCon 2010

    123/216

    Host: twitter.comAuthorization: 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 OKContent-Type: application/x-www-form-urlencoded

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_token_secret=

    25sbmeb6ifnkwx6yp23w55svnof6txdy&oauth_callback_confirmed=true

    2. CLIENT requests a TEMPORARY TOKENfrom SERVER

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    124/216

    3. USER is redirected to SERVER to grantaccess to CLIENT

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    125/216

    HTTP/1.1 302 Redirect

    Location: http://wefollow.com/add/tags?

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

    4. SERVER redirects to USER back to

    CLIENTs oauth_callback

    Thursday, 18 February 2010

    http://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro
  • 8/14/2019 Social Plumbing PyCon 2010

    126/216

    HTTP/1.1 302 Redirect

    Location: http://wefollow.com/add/tags?

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

    4. SERVER redirects to USER back to

    CLIENTs oauth_callback

    Thursday, 18 February 2010

    http://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro
  • 8/14/2019 Social Plumbing PyCon 2010

    127/216

    HTTP/1.1 302 Redirect

    Location: http://wefollow.com/add/tags?

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

    4. SERVER redirects to USER back to

    CLIENTs oauth_callback

    Thursday, 18 February 2010

    http://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro
  • 8/14/2019 Social Plumbing PyCon 2010

    128/216

    HTTP/1.1 302 Redirect

    Location: http://wefollow.com/add/tags?

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

    4. SERVER redirects to USER back to

    CLIENTs oauth_callback

    Thursday, 18 February 2010

    http://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro
  • 8/14/2019 Social Plumbing PyCon 2010

    129/216

    HTTP/1.1 302 Redirect

    Location: http://wefollow.com/add/tags?

    oauth_token=dn8gxxob06rirxzp30e68dsffkd08hfh&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro

    4. SERVER redirects to USER back to

    CLIENTs oauth_callback

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow"

    http://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbErohttp://wefollow.com/add/tags?oauth_token=4GlxjQKGCHMzvkYx66d84T7Uzmu1IPbT1Z2QAiOKg&oauth_verifier=5SXT8kus3yCJbOAkuJTpQ2u9U3Pt67YW0wijyVIbEro
  • 8/14/2019 Social Plumbing PyCon 2010

    130/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow"

  • 8/14/2019 Social Plumbing PyCon 2010

    131/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow"

  • 8/14/2019 Social Plumbing PyCon 2010

    132/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow"

  • 8/14/2019 Social Plumbing PyCon 2010

    133/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow"

  • 8/14/2019 Social Plumbing PyCon 2010

    134/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow",

  • 8/14/2019 Social Plumbing PyCon 2010

    135/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    POST /oauth/access_token HTTP/1.1

    Host: twitter.com

    Authorization: OAuth realm="wefollow",

  • 8/14/2019 Social Plumbing PyCon 2010

    136/216

    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 TEMPORARYCREDENTIALS for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    OAUTH AUTHENTICATION FLOW v1.0aConsumer / Service ProviderPerson Using Web Browser / Manual Entry

  • 8/14/2019 Social Plumbing PyCon 2010

    137/216

    GrantRequest Token

    Direct User toService Provider

    RequestRequest Token

    Obtain UserAuthorization

    Direct User toConsumer

    RequestAccess Token

    GrantAccess Token

    Access ProtectedResources

    A

    B

    C

    D

    E

    F

    G

    ObtainUnau

    thorized

    RequestT

    oken

    UserAuthorizes

    Req

    uestToken

    Exch

    angeRequestToken

    forAccessToken

    Consumer Service Provider A

    B

    C

    D

    E

    F

    G

    Consumer Requests

    Request Token

    Request includes

    oauth_consumer_keyoauth_signature_methodoauth_signatureoauth_timestampoauth_nonceoauth_version (optional)oauth_callback

    Service Provider

    Grants Request Token

    Response includes

    oauth_tokenoauth_token_secretoauth_callback_confirmed

    Consumer Directs User to

    Service Provider

    Request includes

    oauth_token (optional)

    Service Provider Directs

    User to Consumer

    Request includesoauth_tokenoauth_verifier

    Consumer Requests

    Access Token

    Request includes

    oauth_consumer_keyoauth_tokenoauth_signature_methodoauth_signatureoauth_timestampoauth_nonceoauth_version (optional)oauth_verifier

    Service Provider

    GrantsAccess Token

    Response includesoauth_tokenoauth_token_secret

    Consumer Accesses

    Protected Resources

    Request includes

    oauth_consumer_key

    oauth_tokenoauth_signature_methodoauth_signatureoauth_timestampoauth_nonceoauth_version (optional)

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

    Thursday, 18 February 2010

    Hows it work?

    http://blogs.forrester.com/groundswell/2008/03/the-future-of-s.htmlhttp://blogs.forrester.com/groundswell/2008/03/the-future-of-s.html
  • 8/14/2019 Social Plumbing PyCon 2010

    138/216

    How s it work?

    A 3rd party is given a key and a sharedsecret by the API provider.

    3rd party signs the request using a standardalgorithm defined by OAuth.

    API re-signs the request in the samemanner using the known shared secret.

    Two-legged OAuth

    Thursday, 18 February 2010

    Problems with OAuth

  • 8/14/2019 Social Plumbing PyCon 2010

    139/216

    Problems with OAuth

    No way to delegate tokens (e.g. Allowing TwitPicto 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

  • 8/14/2019 Social Plumbing PyCon 2010

    140/216

    Implementing an OAuth Client

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    141/216

    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 TEMPORARYCREDENTIALS from the SERVER

    Thursday, 18 February 2010

    http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    142/216

    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 TEMPORARYCREDENTIALS from the SERVER

    Thursday, 18 February 2010

    http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    143/216

    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 TEMPORARYCREDENTIALS 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)

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    144/216

    p p ( )

    1. CLIENT requests TEMPORARYCREDENTIALS 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)

    http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    145/216

    p p ( )

    1. CLIENT requests TEMPORARYCREDENTIALS 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)

    http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    146/216

    2. CLIENT requests TEMPORARYCREDENTIALS 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.')

  • 8/14/2019 Social Plumbing PyCon 2010

    147/216

    p ( p y )

    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 CLIENTs oauth_callbackwhich 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.')

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    148/216

    p ( p y )

    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 CLIENTs oauth_callbackwhich 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.')

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    149/216

    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 CLIENTs oauth_callbackwhich 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.')

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    150/216

    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 CLIENTs oauth_callbackwhich 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.')

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    151/216

    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 CLIENTs oauth_callbackwhich 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.')

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    152/216

    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 CLIENTs oauth_callbackwhich exchanges TEMPORARY CREDENTIALS

    for TOKEN CREDENTIALS

    Thursday, 18 February 2010

    try:

    credentials = oauth.Token(credentials['oauth_token'],

    http://livepage.apple.com/http://livepage.apple.com/http://livepage.apple.com/
  • 8/14/2019 Social Plumbing PyCon 2010

    153/216

    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 toSERVER on behalf of USER using TOKEN

    CREDENTIALS

    Thursday, 18 February 2010

    try:

    credentials = oauth.Token(credentials['oauth_token'],

    http://twitter.com/account/verify_credentials.json'http://twitter.com/account/verify_credentials.json'
  • 8/14/2019 Social Plumbing PyCon 2010

    154/216

    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 toSERVER on behalf of USER using TOKEN

    CREDENTIALS

    Thursday, 18 February 2010

    try:

    credentials = oauth.Token(credentials['oauth_token'],

    http://twitter.com/account/verify_credentials.json'http://twitter.com/account/verify_credentials.json'
  • 8/14/2019 Social Plumbing PyCon 2010

    155/216

    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 toSERVER on behalf of USER using TOKEN

    CREDENTIALS

    Thursday, 18 February 2010

    http://twitter.com/account/verify_credentials.json'http://twitter.com/account/verify_credentials.json'
  • 8/14/2019 Social Plumbing PyCon 2010

    156/216

    Questions?!

    Thursday, 18 February 2010

  • 8/14/2019 Social Plumbing PyCon 2010

    157/216

    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' '')

  • 8/14/2019 Social Plumbing PyCon 2010

    158/216

    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' '')

  • 8/14/2019 Social Plumbing PyCon 2010

    159/216

    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' '')

  • 8/14/2019 Social Plumbing PyCon 2010

    160/216

    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 H