![]() |
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.
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 |
---|---|
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.
zeep::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);
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.