Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Applications for Tarantool, Part 2: OAuth 2 Authorization via Facebook

DZone's Guide to

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.

· Database Zone ·
Free Resource

RavenDB vs MongoDB: Which is Better? This White Paper compares the two leading NoSQL Document Databases on 9 features to find out which is the best solution for your next project.  

Image title

How 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.

Image title

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.

Image title

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 ID
  • redirect_uri is the redirection URL you specified earlier
  • scope 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!

Get comfortable using NoSQL in a free, self-directed learning course provided by RavenDB. Learn to create fully-functional real-world programs on NoSQL Databases. Register today.

Topics:
database ,tarantool ,tutorial ,authorization ,app development ,oauth 2

Published at DZone with permission of

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}