We write a little OpenID-authorization

    image

    A look into the future


        Recently, all sorts of social networks and, in general, the services of the Internet in terms of attendance and the number of accounts have made a very good habit, in my opinion, - providing unique OpenID-identifiers for users, so that using them you can go to a third-party site. In addition, a very similar, but still not entirely derivative OAuth technology is developing in parallel , which was born thanks to the efforts of the creators of the notorious Twitter and, quoting Wikipedia, “allows third parties to access protected user resources without having to transfer them to (third parties) ) login and password".
        Personally, this trend makes me very happy, and, moreover, I am almost sure that the future lies in such technology. In particular, in the future, new mashups will surely appear for aggregating information from a bunch of sites (in particular, I would like to recall the very good but unfairly forgotten Yahoo Pipes service , which could not win hearts and minds simply because its time had not yet come . Perhaps still to come), and it is precisely such a “form factor” that requires a login to a bunch of services right away.
        Singing praises with such technologies can take a very long time, but personally, for example, I have always been bothered by sites on which I need to register from scratch in order to download something. After all, all of us invariably faced with the fact that when you look for where to download this or that material, it often appears on some completely left and incomprehensible website with the name in the spirit of allbooksmusicwarezzz.omg.su, which also requires registration. No, the point is not piracy, the fact is that there are a lot of sites with all kinds of junk made on the knee. But the human memory for login passwords is limited, and there is nothing you can do. But the nice point here is that many OpenID providers, in addition to information directly used for authorization, can upon request provide basic information about the user - e-mail, full name, preferred language, etc. Moreover, on many of these services, you can control what to give and what to keep in secret. For example, wouldn’t it be pleasant for the user when he went to the next site and saw the friendly inscription “Welcome, Vasya!” In pure Russian, and even the profile was ready for use, along with the avatar, habits and nickname of the dearly beloved cat ?

    Do business and work work


        Enough of the lyrics, I think who needs it as a developer - he already knows all of the above, and ordinary users are unlikely to be interested in further material. Even more eulogies and arguments can be easily found on Ivan Sagalaev’s blog , and let's try to make our authorization system through OpenID (for example, for a blog) in Python, with preference and pianists.
        For my blog, which is currently under development in my Projects folder , I decided to completely abandon the registration and authorization system through a login password, and leave only OpenID. Pylons was chosen as the framework , and for fastening OpenID to Django-projects, a project exists with a simple and understandable name django-openid. For Pylons, in general, there is also a solution called AuthKit , but somehow I didn’t have a very good relationship with it, and all I found on the network were a few snippets, which I had to figure out.
        First you need to install the python-openid module to provide technology support, and then create a controller (request handler by URL, the closest association is the Djangian views.py) and start conjuring.
    $ paster controller auth
        Immediately make a reservation that the code is working to the extent that provides direct authentication, what to do next and how to arrange it all is up to you, gentlemen, creators. The beginning is pretty standard:
    Copy Source | Copy HTML
    1. from openid.consumer.consumer import Consumer, SUCCESS, FAILURE, DiscoveryFailure
    2. from openid.store import filestore
    3. from openid import sreg
    4. from datetime import datetime
    5. from hashlib import md5
    6.  
    7. class AuthController(BaseController):
    8.     def __before__(self):
    9.         self.openid_session = session.get("openid_session", {}) # проверяем, не существует ли openid-сессии
    10.  
    11.     def index(self):
    12.         return render('/accounts/enter.html')
    13.  
    14.     @rest.dispatch_on(POST="signin_POST") # разделяем GET- и POST-запросы по разным обработчикам для удобства
    15.     def signin(self):
    16.         if c.user: # проверяем, не попытался ли уже залогиненый юзер зайти еще раз
    17.             session['message'] = 'Already signed in.'
    18.             session.save()
    19.             redirect(url(action='index')) # и если да, то не пущаем
    20.         session.clear()
    21.         return render('/index.html')


        Now we come to the most interesting:

    Copy Source | Copy HTML
    1. def signin_POST(self):
    2.         problem_msg = 'A problem ocurred comunicating to your OpenID server. Please try again.'
    3.  
    4.         g.openid_store = filestore.FileOpenIDStore('.') # создаем временное хранилище для хранения OpenID-данных, g здесь-массив глобальных переменных Pylons
    5.  
    6.         self.consumer = Consumer(self.openid_session, g.openid_store) # ага, вот и наш клиент
    7.         openid = request.params.get('openid', None) # достаем из запроса строку с OpenID - идентификатором
    8. ...


        Yeah, and here is some magic. SReg is the extension that allows us to request additional user information from the server. The fields whose value I would like to know are listed in the optional list, and additional data, if anything, can always be requested from the user later. If some additional information is required directly from the nose, then you can request it in required, but if the server does not give it away, there will be an error.

    Copy Source | Copy HTML
    1. ...
    2.         sreg_request = sreg.SRegRequest(
    3.             #required=['email'],
    4.             optional=['fullname', 'timezone', 'language', 'email', 'nickname']
    5.         )
    6.  
    7.         if openid is None:
    8.             session['message'] = problem_msg
    9.             session.save()
    10.             return render('/index.html')
    11. ...


        Here I allowed myself a freebie and wrote this code only to explain the difference between a simple OpenID and a cross-login from a Google account. The fact is that Google does not present users with an OpenID identifier of the form vasya_pupkin.google.com , but everything is much simpler and more fun. The authentication URL for all Google users looks exactly the same - www.google.com/accounts/o8/id . It is curious that when you request this URL, Google gives a ready-made XRDS (XML-like type of document returned by the server according to the OpenID 2.0 standard ), which already contains everything you need for authorization, and you as a user are assigned a unique ID, which is, in fact , identifier OpenID.
    Copy Source | Copy HTML
    1. if openid == 'google':
    2.     openid = 'https://www.google.com/accounts/o8/id'
    3.  
    4. try:
    5.     authrequest = self.consumer.begin(openid) # панеслася
    6. except DiscoveryFailure, e: # а вдруг ошибка в адресе или такой провайдер существует только в твоем воображении?
    7.     session['message'] = problem_msg
    8.     session.save()
    9.     return redirect(url(controller='auth', action='signin'))
    10.  
    11. authrequest.addExtension(sreg_request) # подключаем SReg, дабы извлечь требуемые поля для профиля
    12.  
    13. redirecturl = authrequest.redirectURL(h.url_for('/', qualified=True),
    14.     return_to=h.url_for(action='verified', qualified=True),
    15.     immediate=False
    16. ) # после всего, что у нас было с сервером, надо как-то жить дальше
    17. session['openid_session'] = self.openid_session
    18. session.save()
    19. return redirect(url(redirecturl))


    Well, now you can talk to the server whether we will remain friends.

    Copy Source | Copy HTML
    1. ...
    2.     def verified(self):
    3.         problem_msg = 'A problem ocurred comunicating to your OpenID server. Please try again.'
    4.         self.consumer = Consumer(self.openid_session, g.openid_store)
    5.         info = self.consumer.complete(request.params, (h.url_for(controller='auth',
    6.                                                  action='verified',
    7.                                                  qualified=True)))
    8.         if info.status == SUCCESS: # все пучком
    9.  
    10.             sreg_response = sreg.SRegResponse.fromSuccessResponse(info) # извлекаем затребованные в SReg поля
    11.  
    12.             user = User(by_openid=info.identity_url) # ищем юзера по идентификатору в базе
    13.  
    14.             if not user.exist: # а вот тут можно делать что угодно. Например, внести юзера в базу
    15.                 newuser = User()
    16.                 try:
    17.                     email = sreg_response.get('email', u''),
    18.                 except:
    19.                     email = u''
    20.                 newuser.create(
    21.                     openid = unicode(info.identity_url),
    22.                     email = email,
    23.                     password = unicode(md5(info.identity_url).hexdigest()),
    24.                     ip = request.environ['REMOTE_ADDR']
    25.                 )
    26.  
    27.             session.clear() # мутим сессию
    28.             session['openid'] = info.identity_url
    29.             session.save()
    30.  
    31.             if 'redirected_from' in session:
    32.                 red_url = session['redirected_from']
    33.                 del(session['redirected_from'])
    34.                 session.save()
    35.                 return redirect(url(red_url))
    36.             return redirect(url(controller='auth', action='index'))
    37.         else: # факир был пьян
    38.             session['message'] = problem_msg
    39.             session.save()
    40.             return redirect(url(action='signin'))


        That, in fact, is all. What to do with the data received - thrust into cookies, continue registration and ask the user for additional information - it's up to you. Yes, and yet, this code does not work with OpenID from Yahoo . If hunting according to the testament of Kozma Prutkov is disgraced to the root - there is information all in the same blog of Ivan Sagalaev . I will be glad to hear any criticism, clarifications, suggestions. I will try to deal with OAuth in the future and organize a little code for the interested ones on the crosslog from Twitter.

        For the opportunity to scratch my head with an awl, like Master Vinogradinka, I really thank this link and all the comrades who left their snippets there.

    UPD: Habrausermustangostang reveals the secrets of AX (how to get the returned information from Google), because Google doesn’t give away SReg.

    Also popular now: