Tagged with OpenID

Using OpenID, OAuth, OAuth2 and OpenID+OAuth

Over the last year I’ve had an authentication library that I’ve used to slice and dice public services and like most things it’s collected more than it’s share of dust, cruft and other ugly appendages that you wonder if it’ll work then next time you use it.  I’ve been hot and heavy over django (even if it’s embedded inside of Tornado) as a general framework for a while, it’s not broke don’t fix it…

The general case of site authentication looks like this:

  • You need your own username + password
  • You’re perfectly willing to give it all to Facebok/Google/etc. to handle

Depeding on the project I’m quite happy with giving it away, but there are times when you want to have “ownership” of the users on your website.  In which case in this day and age it’s important to allow people to associate their well known credentials with your service — cool.  FYI – This is my “new favorite flow”

  • Ask for email + password + (any service required fields – screen name ….)
  • Require them to associate with  another service
    • Capture picture / full name and other bits from “Facebook”
  • Follow up with prompting to finish profile or other service specific tasks

That’s the simple part, the hard part has been dealing with OpenID, OAuth, OAuth2 and Hybrid protocols.   Since very rarely do you want just to get the fine photo for the user and forget about it.  You probably want to do one of these things:

  • Tweet something they did
  • Check in
  • Add to their facebook page
  • Scrape their friends
  • …etc…

Which means you need to store a token, not only that but some of these wonderful protocols don’t give you persistant identifiers.   Anyway, here’s a bit of commentary and some code excerpts for you to review, hopefully my refactorization makes life more interesting moving forward and those hulking if statements I used to have are gone.

OAuth — The big challenge is that the token you get is a transient identifier, it will change if you assocate account information with this your doomed.   So, typically what you end up needing to do is take your OAuth token and go back and pull the profile, which of course means that you need yet another round trip behind the scenes to get an authentication to happen.

class FoursquareOAuthClient(CommonOAuthClient):
    api_root_url = 'http://foursquare.com' #for testing 'http://term.ie'
    api_root_port = "80"

    #set api urls
    def request_token_url(self):
        return self.api_root_url + '/oauth/request_token'
    def authorize_url(self):
        return self.api_root_url + '/oauth/authorize'
    def access_token_url(self):
        return self.api_root_url + '/oauth/access_token'

class FoursquareBackend(OAuthOpenBackend) :
    name = 'foursquare'
    info = settings.OPENAUTH_DATA.get(name, {})

    oauth_class = FoursquareOAuthClient

    def get_profile(self, token) :
        raw = self._fetch(token, 'http://api.foursquare.com/v1/user.json')

        print raw

        data = json.loads(raw)
        data = data['user']

        identity = 'foursquare:%s' % data['id']

        return identity, {
                        'first_name' : data.get('firstname',''),
                        'last_name'  : data.get('lastname',''),
                        'email'      : data.get('email',''),
                    }, data

OAuth2 — The nice part is that it quick and easy to handshake up a token, but just like OAuth you still need an extra round trip.

class GowallaBackend(OpenBackend) :
    name = 'gowalla'
    info = settings.OPENAUTH_DATA.get(name, {})

    def prompt(self) :
        return 'https://api.gowalla.com/api/oauth/new?%s' % urllib.urlencode({
                                'client_id'    : self.key,
                                'redirect_uri' : self.return_to,
                        })

    def get_access_token(self) :
        url = 'https://api.gowalla.com/api/oauth/token'
        body = {
                    'client_id'     : self.key,
                    'client_secret' : self.secret,
                    'redirect_uri'  : self.return_to,
                    'code'          : self.request.GET['code'],
                    'grant_type'    : 'authorization_code',
        }

        data = self._fetch(url, postdata=body, headers={'Accept':'application/json'})

        vals = json.loads(data)

        return OpenToken(vals['access_token'])

    def get_profile(self, token) :
        from ..libs.gowalla import Gowalla

        go = Gowalla(self.key, access_token=token.token)

        profile = go.user_me()

        identity = "gowalla:%s" % profile['url']

        return identity, {
                        'first_name' : profile['first_name'],
                        'last_name'  : profile['last_name'],
                        'email'      : None,
                    }, profile

OAuth+OpenID — This is where life gets a bit more painful…  Typically over getting all of the URI bits worked out, where things go etc.  We won’t mention strange things like Yahoo doesn’t return the oauth token when asked unless you’ve approved yourself for non-public information…  Gack!

class GoogleBackend(OpenBackend) :
    name = 'google'
    info = settings.OPENAUTH_DATA.get(name,{})

    def _get_client(self) :
        client = consumer.Consumer(self.request.session, util.OpenIDStore())
        client.setAssociationPreference([('HMAC-SHA1', 'no-encryption')])
        return client

    def prompt(self) :
        client = self._get_client()

        auth_request = client.begin('https://www.google.com/accounts/o8/id')

        auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'mode', 'fetch_request')
        auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'required', 'email,firstname,lastname')
        auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.email', 'http://schema.openid.net/contact/email')
        auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.firstname', 'http://axschema.org/namePerson/first')
        auth_request.addExtensionArg('http://openid.net/srv/ax/1.0', 'type.lastname', 'http://axschema.org/namePerson/last')

        auth_request.addExtensionArg('http://specs.openid.net/extensions/oauth/1.0', 'consumer', self.key)
        auth_request.addExtensionArg('http://specs.openid.net/extensions/oauth/1.0', 'scope', 'http://www.google.com/m8/feeds')

        parts     = list(urlparse.urlparse(self.return_to))
        realm     = urlparse.urlunparse(parts[0:2] + [''] * 4)

        return auth_request.redirectURL(realm, self.return_to)

    def get_access_token(self) :
        if self.request.GET.get('openid.mode', None) == 'cancel' or self.request.GET.get('openid.mode', None) != 'id_res' :
            raise OpenBackendDeclineException()

        client = self._get_client()
        auth_response = client.complete(self.request.GET, self.return_to)

        if isinstance(auth_response, consumer.FailureResponse) :
            raise OpenBackendDeclineException("%s" % auth_response)

        ax = auth_response.extensionResponse('http://openid.net/srv/ax/1.0', True)

        self.email      = ax.get('value.email','')
        self.first_name = ax.get('value.firstname','')
        self.last_name  = ax.get('value.lastname','')

        self.identity = auth_response.getSigned(openid.message.OPENID2_NS, 'identity', None)

        otoken  = auth_response.extensionResponse('http://specs.openid.net/extensions/oauth/1.0', True)
        oclient = GoogleOAuthClient(self.key, self.secret)

        tok = oclient.get_access_token(otoken['request_token'])
        return OpenToken(tok['oauth_token'], tok['oauth_token_secret'])

    def get_profile(self, token) :
        v = {
            'email' : self.email,
            'first_name' : self.first_name,
            'last_name' : self.last_name,
        }
        return self.identity, v, v
Tagged ,

Alternative to OpenID

I’ve never been a big fan of OpenID, it feels like an interesting solution to a problem, but not the solution that makes sense.  For starters it requires me to learn something new, which as an “average” user I don’t want to do.

Quick education for those not familiar with OpenID.  OpenID allows you to have a single digital identity that enables you to log into multiple websites using one ID and password.  This means that when I find myself at a signin/signup form I can enter koblas.example.com (it could be as nasty as https://me.yahoo.com/a/nDvVG6521ORdniQiNkMGjNXR3g–) as my identity.

From a practical standpoint it doesn’t make sense to have a second ID (or third, fourth, etc.) in the universe.  I’ve started “enjoying” websites which have given up on the USERNAME concept and just let me use an email address and password to signup/signin.   Now we’re drifting into an interesting world.

What I would like to see is my email address being my unique identifier, after all you’re going to send the account confirmation message there, I’m going to have to confirm it, etc. etc.  Product like Facebook Connect are starting to show up and claim ownership of my identity.  Why not use email and DNS to take over for OpenID.

Here’s a proposed flow:

  • User is at the signin page
  • Enters koblas@example.com
  • DNS query is sent to lookup the “AUTH” record for ‘example.com’
  • result is authhost.example.com
  • Two choices at this point – either handshake ala OpenID or Facebook Connect — OR — have a DNS based auth lookup [the bias is to OpenID]
  • Now pop the user over to authhost.example.com with some arguments to authenticate
  • Return to mysite with cookies and tokens for their continued session.

Best part is that I don’t have to remember a new identifier, DNS handed out the identifier to complete the transaction.  You’re now going, but this will put the control of all identities into the hands of a few.  Sort of, but you can also think about it from the stance that if facebook were to have “public” email then you could use example@facebook.com as an identity for use on other sites.  They get the benefit that part of the authentication contract is that example@facebook.com is a deliverable email address to communicate with you at.

Tagged , ,