📜 ⬆️ ⬇️

Applications for Tarantool. Part 2. OAuth2 authentication

How to build your application for Tarantool and not to make a garden every time you want to do a seemingly elementary thing? This is a continuation of a series of articles on how to create your own applications for Tarantool.


Today we will consider the issues of networking, installation and use of third-party modules.



Content of the cycle “Applications for Tarantool”


- Part 1. Stored procedures
- Part 2. OAuth2 authentication
- Part 3. Testing and running.


Interaction with external services


As an example, consider the implementation of OAuth2 authorization via Facebook in the tarantool-authman application . With OAuth2 authorization, the user follows the link that leads to the login window on the social network. After entering the authorization data and confirming permissions (permisssions), the social network redirects the user back to the site with the authorization code in the GET request parameter. The server must exchange this code for a token (or access and refresh for a pair of tokens). With the token, you can get information about the user from the social network. Learn more about OAuth2 authorization written in this article .



The Tarantool application will take over the exchange of the authorization code (code) for the access token (token) to the user information from the social network, and will also receive user data for this token. In our case, this is email, first name and last name. To exchange the authorization code for the access token, you need to send a request to Facebook with the code, as well as the Facebook application parameters - client_id and client_secret.


Tarantool from version 1.7.4-151 has a built-in http.client module that runs on libcurl . The module allows you to receive and send HTTP requests. We use this module to implement OAuth2 authentication. First, create a helper function for sending HTTP requests in the authman / utils / http.lua module:


 local http = {} local utils = require('authman.utils.utils') local curl_http = require('http.client') -- config —    authman function http.api(config) local api = {} --    local timeout = config.request_timeout function api.request(method, url, params, param_values) local response, body, ok, msg if method == 'POST' then -- utils.format —      placeholder' body = utils.format(params, param_values) --   pcall        ok, msg = pcall(function() response = curl_http.post(url, body, { headers = {['Content-Type'] = 'application/x-www-form-urlencoded'}, timeout = timeout }) end) end return response end return api end return http 

It is worth paying attention to the pcall function. It handles exceptions that occurred during the execution of an anonymous function. In our case, it is necessary to handle the network errors generated by the HTTP client. The result of the pcall call is written to the variables ok (true / false) and msg (error message, nil if successful).


OAuth2 authorization in the application


Create a social model and write a method to get a token using the get_token(provider, code) authorization code, as well as a method to get or update get_profile_info(provider, token, user_tuple) profile data. Consider these methods:


 --    function model.get_token(provider, code) local response, data, token if provider == 'facebook' then --  http —  authman/utils/http.lua response = http.request( 'GET', 'https://graph.facebook.com/v2.8/oauth/access_token', '?client_id=${client_id}&redirect_uri=${redirect_uri}&client_secret=${client_secret}&code=${code}', { -- config —  ,       --       client_id = config[provider].client_id, redirect_uri = config[provider].redirect_uri, client_secret = config[provider].client_secret, code = code, } ) if response == nil or response.code ~= 200 then return nil else data = json.decode(response.body) return data.access_token end end end --     function model.get_profile_info(provider, token, user_tuple) local response, data user_tuple[user.PROFILE] = {} if provider == 'facebook' then response = http.request( 'GET', 'https://graph.facebook.com/me', '?access_token=${token}&fields=email,first_name,last_name', { token = token } ) if response == nil or response.code ~= 200 then return nil else data = json.decode(response.body) user_tuple[user.EMAIL] = data.email user_tuple[user.PROFILE][user.PROFILE_FIRST_NAME] = data.first_name user_tuple[user.PROFILE][user.PROFILE_LAST_NAME] = data.last_name return data.id end end end 

Now add an API method to the application that allows you to create a user or log in under an existing one through Facebook. The method will return the user along with the session data. More information about how the session is formed and validated can be found in the source code .


 --  api  authman/init.lua function api.social_auth(provider, code) local token, social_id, social_tuple local user_tuple = {} if not (validator.provider(provider) and validator.not_empty_string(code)) then return response.error(error.WRONG_PROVIDER) end --  OAuth2- token = social.get_token(provider, code, user_tuple) if not validator.not_empty_string(token) then return response.error(error.WRONG_AUTH_CODE) end --     social_id = social.get_profile_info(provider, token, user_tuple) if not validator.not_empty_string(social_id) then return response.error(error.SOCIAL_AUTH_ERROR) end user_tuple[user.EMAIL] = utils.lower(user_tuple[user.EMAIL]) user_tuple[user.IS_ACTIVE] = true user_tuple[user.TYPE] = user.SOCIAL_TYPE -- ,    space     social_id social_tuple = social.get_by_social_id(social_id, provider) if social_tuple == nil then --   —   user_tuple = user.create(user_tuple) social_tuple = social.create({ [social.USER_ID] = user_tuple[user.ID], [social.PROVIDER] = provider, [social.SOCIAL_ID] = social_id, [social.TOKEN] = token }) else --    —    user_tuple[user.ID] = social_tuple[social.USER_ID] user_tuple = user.create_or_update(user_tuple) social_tuple = social.update({ [social.ID] = social_tuple[social.ID], [social.USER_ID] = user_tuple[user.ID], [social.TOKEN] = token }) end --    local new_session = session.create( user_tuple[user.ID], session.SOCIAL_SESSION_TYPE, social_tuple[social.ID] ) return response.ok(user.serialize(user_tuple, { session = new_session, social = social.serialize(social_tuple), })) end 

How to verify that the method works? First you need to register the application in Facebook. We do this on the Facebook developer page . In the created application, you need to add the “Login through Facebook” product and specify redirect_uri - “Valid URLs for OAuth redirection”. The redirect_uri parameter is the URL of your site, where the social network will redirect the user with the code parameter after successful authorization in the social network. Then open the URL in your browser https://www.facebook.com/v2.8/dialog/oauth?client_id=${client_id}&redirect_uri=${redirect_uri}&scope=email , where


• client_id - id of your application on Facebook;
• redirect_uri - url for the redirect you specified earlier;
• scope - a list of permissions (in this case, only email).


Facebook will ask for confirmation of permissions, after confirmation it will redirect you with the code GET parameter. This is the same authorization code that the api.social_auth() method api.social_auth() . Before checking the performance of the code, create the configuration file authman / config / config.lua, in which we specify the settings of the Facebook application.


 return { facebook = { client_id = 'id from fb application', client_secret = 'secret from fb application'', redirect_uri='http://redirect_to_your_service', } } 

Now check that the code works and the application receives information about the user from the social network:


 $ tarantool version 1.7.4-384-g70898fd type 'help' for interactive help tarantool> config = require('config.config') tarantool> box.cfg({listen = 3331}) tarantool> auth = require('authman').api(config) tarantool> code = 'auth_code_from_get_param' tarantool> ok, user = auth.social_auth('facebook', code) tarantool> user --- - is_active: true social: provider: facebook social_id: '000000000000001' profile: {'first_name': '', 'last_name': ''} id: b1e1fe02-47a2-41c6-ac8e-44dae71cde5e email: ivanov@mail.ru session: ... ... 

Installing Third-Party Modules


To solve many problems, it is good to have ready-made solutions on hand. For example, in versions of Tarantool below 1.7.4-151, an HTTP request "out of the box" could not be sent. The tarantool-curl module was required. Now this module is no longer supported, it is not recommended to use it. However, there are many other useful modules, one of which is tarantool-queue - the implementation of a FIFO queue.


There are several ways to install tarantool-queue. The first, the easiest and most convenient, appeared relatively recently, in the version Tarantool 1.7.4-294.


 $ tarantoolctl rocks install queue 

Other Tarantool packages are also available for installation using the package manager. A complete list of modules for Tarantool can be found on the Rocks page .


The second way is using the package manager of your OS. Here you need to connect the Tarantool repository, if you have not yet connected it at the installation stage , and also make sure that the corresponding package is in the repository. For example, for Ubuntu:


 $ sudo apt-get install tarantool-queue 

The third method is more difficult, however, it allows using not only applications for Tarantool, but also ready-made Lua modules. It is convenient to install modules for Tarantool and Lua using the LuaRocks package manager. Details about it and the available modules can be found in the documentation . Install LuaRocks and configure it to work with the Tarantool repository:


 $ sudo apt-get install luarocks 

Now we will configure LuaRocks to install not only Lua-packages, but also packages for Tarantool. To do this, you need to create a file ~ / .luarocks / config.lua with the following contents:


 rocks_servers = { [[http://luarocks.org/repositories/rocks]], [[http://rocks.tarantool.org/]] } 

Install the module itself and check its work:


 #  tarantool-queue $ sudo luarocks install queue #       : $ tarantool version 1.7.3-433-gef900f2 type 'help' for interactive help tarantool> box.cfg({listen = 3331}) tarantool> queue = require('queue') tarantool> test_queue = queue.create_tube('test_queue', 'fifo') tarantool> test_queue:put({'task_1'}) tarantool> test_queue:put({'task_2'}) tarantool> test_queue:take() --- - [0, 't', ['task_1']] ... 

So, now we can create applications with complex architecture and external interactions. In the next part, we will look at application testing, as well as configuration and launch in battle. See you again!


')

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


All Articles