C++ REST SDK sessions

Tags: , ,

This article describes and provides an implementation of sessions in C++ REST SDK (Casablanca).

Sessions are used to manage user roles and permissions. They can be used as an interface to manage “session data” or may be a way of clustering services.

This article may be helpful for building an authentication service in Casablanca.

Resources

The provided code has been tested on Linux and MacOS X.
The given examples use native c++ objects to store data, I did it this way in case you don't want to use external libraries. In case you would like to use Redis to store data I included classes that give you this possibility, to integrate it I used redisclient by Alex Nekipelov.

Schema

Session classes C++ REST SDK

Sessions are unique, they are stored and retrieved thanks to a token passed through a cookie, a JSON or a query string.

All sessions inherit from a Session abstract class.

Their life cycle and storage is managed by a Session Handler. The basic life of a session consists of three steps: Open, Update and Close. Session handlers implement the SessionHandler interface.

All the objects are configured thanks to configuration properties, I use a configuration file for storing them but as configuration properties are accessed through the Session Handler, you can use other support as c++ constants to store them.

Code

Retrieving session:

void TestController::handle_post(web::http::http_request request)
{
  web::http::http_response response;
  granada::http::session::MapSession storage_session(request,response);
  //  ...
}

If you are using a cookie to store the token and session does not exist or is timed out, a new session will be opened.

If you are using JSON or query strings to pass the token, this is the way of retrieving, opening and closing a session:

// Retrieve session if it exists, if it does not, no session is created
granada::http::session::MapSession storage_session(request,response);

// check if a session is opened testing its token
if (storage_session.GetToken().empty()){

  // session does not have a token, session is not opened
  // open it.
  storage_session.Open();

}else{

  // session is already opened

}

// close the session.
storage_session.Close();

In this example MapSession internally uses a Session Handler (MapSessionHandler) to retrieve the stored session values, as well as to update and close itself.

Session Roles

Sessions may have roles, roles are used to manage user permissions, for example letting or not the user to access some data.

Different sessions may use different role implementations. Session Role classes implement the Session Roles interface, so you can implement them as you want, in the given examples roles and role properties are stored in a map but you may want to implement a role class that use for example a database to store roles and its properties.

// adding USER role
storage_session.roles()->Add("USER");

// setting role property
storage_session.roles()->SetProperty("USER","username",username);

// retrieve role property
std::string username = storage_session.roles()->GetProperty("USER","username");

// destroy role property
storage_session.roles()->DestroyProperty("USER","username");

// check session roles
if (storage_session.roles()->Is("USER")){
  // User has the permission to access data.
}else{
  // cannot access data.
}

// remove role
storage_session.roles()->Remove("USER");

// at this point user won't be able to access data.

This way we can generate a token so other applications can access our user data, we can do it this way:

void AuthController::handle_post(web::http::http_request request)
{

  // Open a session an deliver the token to the client
  // so any application using this token can read user
  // messages.

  web::http::http_response response;
  granada::http::session::MapSession simple_session(request,response);

  // open the session. It will create a token and store the session.
  simple_session.Open();

  // Add the role that will give the user the privilege of reading
  // messages.
  simple_session.roles()->Add("MESSENGER_READER");

  // deliver the session token to the user client so he can share
  // it with other applications that will read his messages.
  response.set_body("{\"token\":\"" + simple_session.GetToken() + "\"}");
  response.set_status_code(status_codes::OK);
  response.headers().add(U("Content-Type"), "text/json; charset=utf-8");
  request.reply(response);
}

Any client using this token will now have the role MESSENGER_READER and the permission to read the user messages.

Session Factory

These classes apart from being useful for instantiating sessions can be used for centralizing the sessions setting, which in most cases means “check if session exists and is valid and create a new one if it is not”. As session factories implement Session Factory we can benefit from polymorphism and “check” our sessions without knowing their type.

Session data

All sessions inheriting from Session can manage “session data”.
Different sessions may use different data storage implementations. The data storage manager classes inherit from the CacheHandler abstract class, they are called drivers, in the implemented libraries you can see there is a SharedMapCacheDriver that stores data in a map shared by all sessions and a RedisCacheDriver that stores data using Redis data structure server.

// store some session data, store a key-value pair
storage_session.Write("test","hello world!!!");

// retrieve session data
std::string my_var = storage_session.Read("test");
// my_var is equal to “hello world!!!”

// destroy session data
storage_session.Destroy("test");
I have not included the CacheHandler or Cache drivers in the schema above as it is not really part of the session architecture.

Conclusions

Concurrency

Due to C++ REST SDK design we can have multiple threads using the same session. So be careful when opening, deleting, modifying roles or session data, the last modification will be the one stored.

If your client makes asynchronous calls, take care of using a session factory to centralize the initialization of the session at the beginning of the application (as it is done in the provided cart example), so only one session-token is created.

Using Redis

I recommend using Redis to store session data or even sessions. Redis has a lot of features already implemented that can be very powerful and will save us lots of time.

Next steps

As I have already mentioned, sessions will help us to implement an authentication service in Casablanca. I would also find interesting the implementation of custom user services clustered by sessions.


Please contact if you want to discuss about the code:
- Twitter
- Mail: afernandez [at] cookinapps.io