📜 ⬆️ ⬇️

OAuth authorization using the example of Desktop Twitter client

It took me here to write a cross-platform Twitter client with closed source code, do not ask why I need it, I have this job, I get money for it. Which is logical, C ++ was chosen as the development language using Qt.
API Twitter'a itself is simple as a tarpaulin boot. But! There is such an important thing as authorization, and there are two ways, the old one is authentication via HTTP Headers and a new one is using OAuth protocol. The old method is simple, just like the API itself, but, unfortunately, it is not safe, and most importantly, the Twitter team warns that it will abandon it at the end of June of this year. Therefore, the second OAuth method remains. I must say that this protocol is used not only in Twitter, but since I wrote Twitter-client, and we will consider the example of Twitter'a.

Theory


So, having studied the Twitter API and the OAuth protocol description, I learned the following. To authorize a Desktop application, you need to register your future application on Twitter, this can be done on this page: twitter.com/oauth_clients , you will be given two keys: oauth_consumer_key and oauth_consumer_secret. These keys should be remembered and subsequently inserted into your application as constants, since they are inseparably linked with it.

Now consider the authorization process in steps:
  1. Using a GET request to api.twitter.com/oauth/request_token, we need to get the initial value of the oauth_token and oauth_secret keys
  2. Using a GET request, we need to open the following page in the user's browser: api.twitter.com/oauth/authorize
  3. On this page, the user will be asked for his username and password if he is not authorized on the site and will be asked if he really wants to allow this application to access Twitter on behalf of his account.
  4. After the user's consent, he will be shown a PIN code
  5. In the meantime, our application should offer the user a dialog for entering a PIN code.
  6. After the user copies the PIN from the browser and inserts it into our application, it must execute a certain POST request to the address api.twitter.com/oauth/access_token , this is necessary to obtain the real oauth_token and oauth_secret keys that will be used later to identify the user in the system.

Now more about requests, I recall three of them: request_token, autorize and access_token

request_token


This request should contain the following field in the HTTP header:
Authorization: OAuth realm = "http% 3A% 2F% 2Fapi.twitter.com% 2F",
oauth_consumer_key = "0685bd9184jfhq22",
oauth_signature_method = "HMAC-SHA1",
oauth_timestamp = "137131200",
oauth_nonce = "4572616e48616d6d65724c61686176",
oauth_version = "1.0",
oauth_signature = "wOJIO9A2W5mFwDgiDvZbTSMK% 2FPY% 3D"

realm - the host to which the request is sent;
oauth_consumer_key - the same key that we were given during registration;
oauth_signature_method is a signature encryption method, there are three of them for this protocol: PLAINTEXT, HMAC-SHA1, RSA-SHA1, but it was found out experimentally that Twitter does not support PLAINTEXT;
oauth_timestamp - the current timestamp on your machine;
oauth_nonce - randomly generated string for each request, a kind of salt;
oauth_version - protocol version, always 1.0;
oauth_signature - oh! This parameter is the most intricate, it is formed as follows:
We need to run HMAC-SHA1 (or RSA-SHA1) with a key that is formed like this: urlencode ("<oauth_consumer_secret> & <oauth_token_secret>") - (at this stage, oauth_token_secret has not yet been received, so it will be empty) and the base line, which is composed as follows: "<request method> & <urlencode (request address)> & <urlencode (key_sort (request parameters))>" so that I can give an example more clearly:
GET & http3A% 2F2Fapi.twitter.com% 2Frequest_token & oauth_consumer_key% 3Ddpf43f3p2l4k3l03% 26oauth_nonce% 3Dkllo9940pd9333jh% 26oauth_signature_method% 3DHMAC-SHA4A aracrynl aus a%%% a% _% a%%%%%%% %ver%% %verververver%%%%%%%%%%%%%%%%%%% %nnnllllllllllllllllllllllllllllll a

and an example key:
kd94hf93k423kf44% 26

After that, the resulting value should be processed by the base64 algorithm.
')
Response of this request, if everything went well, will contain a line of the form:
oauth_token = nnch734d00sl2jdk & oauth_token_secret = pfkkdhi9sl3r4s00

These parameters should be parsed and memorized.

autorize


The easiest step, you need to open the following request in your browser:
api.twitter.com/oauth/authorize?oauth_token=nnch734d00sl2jdk

The result of the user's actions in the browser will be the PIN code displayed to him.
Your application must request this PIN and remember it; you will need it in the next step.

access_token


The final authorization step, now we need to get the real keys oauth_token and oauth_token_secret.
To do this, you must perform a POST request to the address: api.twitter.com/oauth/access_token .
POST parameters should contain the following:
oauth_consumer_key = dpf43f3p2l4k3l03
oauth_token = hh5s93j4hdidpola
oauth_signature_method = HMAC-SHA1
oauth_timestamp = 1191242092 &
oauth_version = 1.0
oauth_nonce = dji430splmx33448
oauth_verifier = 213423534
oauth_signature = kd94hf93k423kf44% 26hdhd0244k9j7ao03

oauth_verifier is the PIN we received.
The signature is generated in the same way as in the first step.
The answer to this query will be the same string with parameters as in the first step, but these parameters need to be remembered forever (unless, of course, you want the user to go through the whole procedure each time the program is started).

Practice


Practice has shown that there is no sane implementation of HMAC-SHA1 in C ++ under the LGPL, and this is sad, I tried to do something with this implementation: bit.ly/96RlAL - I did not succeed. There is an option to write my own using OpenSSL, unfortunately I did not have time for that. However, during intensive googling I found a library called QOAuth

QOAuth


With luck, I didn’t have a side chapel :), however, even with her everything was not so simple.
The library itself is included in the project, it is very easy and simple to compile (by the way I did not manage to make it work as a separate * .so / *. Dll file, but this was already due to the lack of time and curvature of my hands), but this library has dependencies, and namely QCA - Qt Cryptographic Architecture and its QCA OpenSSL plugin.

QCA and QCA ossl plugin


The assembly of the QCA itself is trivial, all steps are described in the README, however, the ossl-plugin refused to take off after taking off with this message:
qca-ossl.cpp: In function 'X509_EXTENSION *
opensslQCAPlugin :: new_subject_key_id (X509 *) ':
qca-ossl.cpp: 330: warning: deprecated conversion from string constant to
'char *'
qca-ossl.cpp: In member function 'virtual QCA :: Provider :: Context *
opensslProvider :: createContext (const QString &) ':
qca-ossl.cpp: 6815: error: 'EVP_whirlpool'
*** Error code 1

And then I felt that this was the end: (however, the tass habrouser came to the rescue and helped to google the solution: www.mail-archive.com/kde-freebsd@kde.org/msg03672.html - here it is a patch that resolves the error (note patch for KDE under FreeBSD :)). Well, the simplest thing remains, to put everything together and write the request code, the code was taken from QOAuth examples and modified, because the examples are outdated, unlike the library itself, and become “noncompilable”, therefore I attach examples of working request_token and access_token

request_token


  1. QOAuth :: Interface * qoauth = new QOAuth :: Interface ( this );
  2. qoauth-> setConsumerKey (consumerKey.toAscii ());
  3. qoauth-> setConsumerSecret (consumerSecret.toAscii ());
  4. qoauth-> setRequestTimeout (10000);
  5. QOAuth :: ParamMap reply = qoauth-> requestToken (oAuthRequestTokenUrl, QOAuth :: GET, QOAuth :: HMAC_SHA1);
  6. if (qoauth-> error () == QOAuth :: NoError)
  7. {
  8. token = reply. value (QOAuth :: tokenParameterName ());
  9. tokenSecret = reply. value (QOAuth :: tokenSecretParameterName ());
  10. }
* This source code was highlighted with Source Code Highlighter .


acess_token


  1. QOAuth :: ParamMap otherArgs;
  2. otherArgs.insert (QByteArray ( "oauth_verifier" ), pin.toAscii ());
  3. QOAuth :: ParamMap reply = qoauth-> accessToken (oAuthAccessTokenUrl, QOAuth :: POST, token, tokenSecret, QOAuth :: HMAC_SHA1, otherArgs);
  4. if (qoauth-> error () == QOAuth :: NoError) {
  5. token = reply. value (QOAuth :: tokenParameterName ());
  6. tokenSecret = reply. value (QOAuth :: tokenSecretParameterName ());
  7. QString userName = reply. value ( "screen_name" );
  8. }
* This source code was highlighted with Source Code Highlighter .


useful links


Twitter API
OAuth protocol

Conclusion


I, as in all my other articles, do not pretend to complete the solution, and do not offer a step-by-step guide, I only describe the basic sequence of actions necessary to solve the problem and the pitfalls that I faced myself. I will also be grateful if someone prompts a working cross-platform LGPL implementation of HMAC-SHA1.

Source: https://habr.com/ru/post/86846/


All Articles