libzeep

PrevUpHomeNext

Security

In a web application it is often required to limit access to certain URI's to authorized users. To facilitate this in libzeep, the zeep::http::server object can use a security_context. The security_context itself uses a user_service to provide user_details structs containing the actual data for a user.

The user_details structure contains the username, encrypted password and a list of roles this user can play. The roles are simple text strings and should preferrably be very short, like 'USER' or 'ADMIN'.

The user_service class returns user_details based on a username via the pure virtual method load_user. A simple_user_service class is available to create very simple user services based on static data. In a real world application you should implement your own user_service and store user information in e.g. a database.

example

Let us walk through an example of an application using security. This web application will have two pages, a landing page at the URI / (but also at /index and /index.html) and an admin page at /admin. The latter of course will only be accessible by our admin who is called scott and he uses the password tiger.

[Note] Note

The code for all example code used in this documentation can be found in the doc/examples subdirectory of the libzeep distribution.

First start by writing some template code. For this example we will have a common menu template and two templates for the two pages respectively. The interesting part of the menu template is this:

<div z:fragment="menu" class="w3-bar w3-border w3-light-grey">
  <a href="/" class="w3-bar-item w3-button">Home</a>
  <a href="/admin" class="w3-bar-item w3-button"
  	 z:classappend="${#security.hasRole('ADMIN') ? '' : 'w3-text-grey w3-hover-none w3-hover-text-grey'}">Admin</a>
  <a z:if="${not #security.authorized()}" href="/login" class="w3-bar-item w3-button w3-green w3-right">Login</a>
  <a z:if="${#security.authorized()}" href="/logout" class="w3-bar-item w3-button w3-green w3-right">Logout</a>
</div>

We're using W3.CSS as CSS library, albeit we have stored a copy in our own docroot. The two last links in this navigation bar have the z:if"..."= argument checking whether the current user is authorized. These attributes help in select which of the two will be visible, login or logout, based on the current authentication state. The #security class in our el library has two more methods called username and hasRole. The last one returns true when a user has the role asked for.

Next we define a simple html_controller that handles the two pages and also serves stylesheets and scripts.

class hello_controller : public zeep::http::html_controller
{
  public:
    hello_controller()
    {
        // Mount the handler `handle_index` on /, /index and /index.html
        mount("{,index,index.html}", &hello_controller::handle_index);

        // This admin page will only be accessible by authorized users
        mount("admin", &hello_controller::handle_admin);

        // scripts & css
        mount("{css,scripts}/", &hello_controller::handle_file);
    }

    void handle_index(const zeep::http::request& req, const zeep::http::scope& scope, zeep::http::reply& rep)
    {
        get_template_processor().create_reply_from_template("security-hello.xhtml", scope, rep);
    }

    void handle_admin(const zeep::http::request& req, const zeep::http::scope& scope, zeep::http::reply& rep)
    {
        get_template_processor().create_reply_from_template("security-admin.xhtml", scope, rep);
    }
};

Nothing fancy here, just a simple controller returning pages based on templates. In the template we add a salutation:

<p>Hello, <span z:text="${#security.username() ?: 'world'}"></span>!</p>

And here we see the call to #security.username(). Note also the use of the elvis operator, if username is not set, 'world' is used instead.

Now, in the main of our application we first create a user_service.

// Create a user service with a single user
zeep::http::simple_user_service users({
    {
        "scott",
        zeep::http::pbkdf2_sha256_password_encoder().encode("tiger"),
        { "USER", "ADMIN" }
    }
});

We use the simple_user_service class and provide a static list of users. The user_service should return user_details with an encrypted password and therefore we encrypt the plain text password here. Normally you would store this password encrypted of course. For encrypting password we use the pbkdf2_sha256_password_encoder class. You can add other password encoders based on other algorithms like bcrypt but you then have to register these yourself using security_context::register_password_encoder;

Now we can create the security context. This context will be passed to the zeep::http::server class as a pointer and the zeep::http::server will take ownership.

// Create a security context with a secret and users
std::string secret = zeep::random_hash();
auto sc = new zeep::http::security_context(secret, users, false);

The secret passed to the security_context is used in creating signatures for the JWT token. If you store this secret, the sessions of your users will persist reboots of the server. But in this case we create a new secret after each launch and thus the tokens will expire.

Now we add access rules.

// Add the rule,
sc->add_rule("/admin", "ADMIN");
sc->add_rule("/", {});

A rule specifies for a glob pattern which users can access it based on the roles these users have. If the list of roles is empty, this means all users should be able to access this URI. When a request is received, the rules are checked in order of which they were added. The first match will be used to check the roles.

In this example /admin is only accessible by users having role ADMIN. All other URI's are allowed by everyone. Note that we could have also created the security_context with the parameter defaultAccessAllowed as true, we then would not have needed that last rule.

And now we can create the zeep::http::server, add some controller and start it.

1zeep::http::server srv(sc, "docroot");

srv.add_controller(new hello_controller());
srv.add_controller(new zeep::http::login_controller());

srv.bind("localhost", 8080);
srv.run(2);

1

Use the server constructor that takes the path to a docroot so it will construct a template processor

Note that we add the default login_controller. This controller takes care of handling /login and /logout requests. It will also add the required rule for /login to the security_context using add_rule("/login", {}); since otherwise the login page would not be reachable. Make sure you do not add your own rules that prevent access to this page.

And that's all. You can now start this server and see that you can access / and /login without any problem but /admin will give you an authentication error. When you login using the credentials scott/tiger you can access the /admin page and you can now also click the Logout button.


PrevUpHomeNext