Applications for Tarantool, Part 2: OAuth 2 Authorization via Facebook
See how to build your own Tarantool app without overcomplicating things by learning about networking as well as installing and using third-party Tarantool packages.
Join the DZone community and get the full member experience.
Join For FreeHow do you build your own Tarantool application without overcomplicating things? This second tutorial will cover networking as well as installing and using third-party Tarantool packages.
If you missed Part 1, you can find it here.
Interaction With External Services
Let’s take a look at how OAuth 2 authorization via Facebook is implemented using the tarantool-authman application. With OAuth 2 authorization, a user clicks a link that takes them to a Facebook login page. After the user enters their authorization credentials and grants necessary permissions, the social network redirects them back to the site with an authorization code embedded into the redirection URI as a GET
parameter. The server then exchanges this code for a token (or a pair of tokens — access
and refresh
), which allows the obtaining of information about the user directly from Facebook. To learn more about how OAuth 2 works, see this article.
The Tarantool application handles the code-for-token exchange and uses the token to get information about the user from Facebook; in our case, the information includes the user’s email and first and last name. To exchange an authorization code for a token, it is necessary to send Facebook an HTTP request containing the code itself, along with the application’s Facebook parameters, client_id
and client_secret
.
Tarantool 1.7 has a built-in http.client
package that is based on libcurl
. This package allows for the receiving and sending of HTTP requests, so let’s use it to implement OAuth 2. We’ll begin by creating a helper function for sending HTTP requests in the authman/utils/http.lua
package:
local http = {}
local utils = require(‘authman.utils.utils’)
local curl_http = require(‘http.client’)
-- config — general configuration for the authman application
function http.api(config)
local api = {}
-- Configuring network requests
local timeout = config.request_timeout
function api.request(method, url, params, param_values)
local response, body, ok, msg
if method == ‘POST’ then
-- utils.format — helper function for placeholder
-- substitution
body = utils.format(params, param_values)
-- Safe call to pcall won’t interrupt the program
-- execution in case of a network error
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
In-App OAuth 2 Authorization
Let’s create a social
model and implement a get_token(provider, code)
method for obtaining a token by an authorization code, and a get_profile_info(provider, token, user_tuple)
method for getting and updating information about a given user. Below is the corresponding code:
-- Method for obtaining a token
function model.get_token(provider, code)
local response, data, token
if provider == ‘facebook’ then
-- In this context, http is the authman/utils/http.lua
-- package
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 — project configuration passed to the
-- model upon initialization
-- Application’s parameters in the social network
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
-- Method for getting a user profile
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}&,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, let’s design an API method for creating a new user or for logging in via Facebook with an existing one. This method should return a user and session data. To learn more about how sessions are created and validated, have a look at the source code here.
-- API method in 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
-- Getting an OAuth 2 token
token = social.get_token(provider, code, user_tuple)
if not validator.not_empty_string(token) then
return response.error(error.WRONG_AUTH_CODE)
end
-- Getting information about a user
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
-- Checking if our space already has a user with this social_id
social_tuple = social.get_by_social_id(social_id, provider)
if social_tuple == nil then
-- If not, creating a new user
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
-- If it does have a user, updating a user profile
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
-- Creating a user session
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 do we check if this method really works? For starters, it’s necessary to register the application on the Facebook for Developers portal. We need to add a Facebook Login and, in the Valid OAuth redirect URIs field, specify redirect_uri
, which is the URL of your site where the social network redirects users with an authorization code once they’re successfully authorized in the social network. After that, in a web browser, open the following URL:
client_id
is your application’s Facebook IDredirect_uri
is the redirection URL you specified earlierscope
is a list of permissions (in our case, only email)
Facebook will then ask you to confirm you’re granting it the listed permissions and following your confirmation, will redirect you with an authorization code embedded into the redirection URL as a GET
parameter. This is the very same authorization code that is passed to the api.social_auth()
method. Before we check if our code works as expected, let’s create authman/config/config.lua
, a configuration file holding the application’s Facebook parameters.
return {
facebook = {
client_id = ‘id from fb application’,
client_secret = ‘secret from fb application’,
redirect_uri=‘http://redirect_to_your_service’,
}
}
Now, we can make sure that our code works and that the application obtains a user profile 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’: ‘Ivan’, ‘last_name’: ‘Ivanov’}
id: b1e1fe02–47a2–41c6-ac8e-44dae71cde5e
email: ivanov@mail.ru
session: …
…
Installing Additional Packages
It’s always nice to have readily available solutions when dealing with various problems. For example, in Tarantool versions older than 1.7.4–151, it was impossible to send an HTTP request out of the box — you needed a tarantool-curl package for it (deprecated now). There are many other useful packages out there, and one of them is tarantool-queue, which implements a FIFO queue.
There are several ways to install this package, but the simplest one has appeared in Tarantool 1.7.4–294:
$ tarantoolctl rocks install queue
Other Tarantool packages can also be installed via the package manager. The complete list of Tarantool packages is available on the Rocks page.
Another way of installing tarantool-queue is via a package manager that comes with your OS. You need to add the Tarantool repository to the list of available repositories, if you haven’t done so already during the installation, and make sure that the package you need is indeed in the repository. For example, if you’re on Ubuntu, do this:
$ sudo apt-get install tarantool-queue
A third installation method is somewhat more complicated, but it allows using not only Tarantool applications, but also Lua packages, both of which are easy to install with a LuaRocks package manager. You can refer to the documentation to learn more about LuaRocks and available packages. Let’s install LuaRocks and configure it to work with the Tarantool repository:
$ sudo apt-get install luarocks
Now we need to configure LuaRocks so that we can install not only Lua packages, but Tarantool packages as well. To do that, we create a ~/.luarocks/config.lua
file with the following settings:
rocks_servers = {
[[http://luarocks.org/repositories/rocks]],
[[http://rocks.tarantool.org/]]
}
Finally, we can install tarantool-queue
and check if it works:
# Installing tarantool-queue
$ sudo luarocks install queue
# Launching the interactive console and checking if the package works:
$ 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’re able to create complex applications that interact with external services. Thanks for reading and stay tuned!
Published at DZone with permission of , DZone MVB. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments