Plugins and classes to authenticate users.
More...
The way to manage user authentication in Cutelyst is to combine a AuthenticationStore that provides the user data from for example a database together with a AuthenticationCredential validator that checks the password or other information to legitimate a user in an AuthenticationRealm that is added to the main plugin class Authentication in your application’s init() method.
Implementation example
You can add multpiple AuthenticationRealms to be used for different parts of your application. We will use one realm for the website and one for API access in this example. We will assume that you store your user data in a MySQL/MariaDB database with the following layout for your users
table:
Row | Type |
id | INTEGER PRIMARY KEY |
username | TEXT |
password | TEXT |
email_address | TEXT |
first_name | TEXT |
last_name | TEXT |
active | INTEGER |
In order to persist the authenticated user over multiple requests, you also need the Session plugin. Include it together with the Authentication plugin in your CMakeLists.txt file. For this example we also need the Cutelyst::Sql plugin.
target_link_libraries(MyApp
...
Cutelyst::Utils::Sql
...
)
Main class to manage user authentication.
Plugin providing methods for session management.
Create an authentication store
In order to get the user data from the database we have to create our own subclass of AuthenticationStore and reimplement the findUser() method. We will call it AuthStoreSql in this example.
The header file authstoresql.h looks like the following:
#ifndef AUTHSTORESQL_H
#define AUTHSTORESQL_H
#include <Cutelyst/Plugins/Authentication/authenticationstore.h>
class AuthStoreSql : public AuthenticationStore
{
public:
AuthStoreSql();
~AuthStoreSql() override = default;
AuthenticationUser findUser(Context *c,
const ParamsMultiMap &userinfo)
override;
private:
};
#endif
QMultiMap< QString, QString > ParamsMultiMap
The Cutelyst namespace holds all public Cutelyst API.
Our implementation in authstoresql.cpp:
#include "authstoresql.h"
#include <Cutelyst/Plugins/Utils/Sql/Sql>
AuthStoreSql::AuthStoreSql() : AuthenticationStore()
{
m_idField = "username";
}
AuthenticationUser AuthStoreSql::findUser(Context *c,
const ParamsMultiMap &userinfo)
{
const QString username = userinfo[m_idField];
qDebug() <<
"FOUND USER ->" << userId.
toInt();
AuthenticationUser user(userId);
for (int i = 0; i < columns; ++i) {
}
for (int i = 0; i < columns; ++i) {
user.inser(cols.
at(i), query.
value(i));
}
return user;
}
return {};
}
#define CPreparedSqlQueryThread(str)
QList::const_reference at(qsizetype i) const const
QString text() const const
void bindValue(const QString &placeholder, const QVariant &val, QSql::ParamType paramType)
QSqlError lastError() const const
QSqlRecord record() const const
QVariant value(const QString &name) const const
QString fieldName(int index) const const
int toInt(bool *ok) const const
Configure authentication
We now have to glue all the stuff together in our application’s init method. Example myapp.cpp:
#include <Cutelyst/Plugins/Session/Session>
#include <Cutelyst/Plugins/Authentication/authentication.h>
#include <Cutelyst/Plugins/Authentication/credentialpassword.h>
#include <Cutelyst/Plugins/Authentication/credentialhttp.h>
#include "authstoresql.h"
bool MyApp::init()
{
...
new Session(this);
auto auth = new Authentication(this);
auto authStore = std::make_shared<AuthStoreSql>();
auto credWeb = std::make_shared<CredentialPassword>();
auth->addRealm(authStore, credWeb, "website");
auto credApi = std::make_shared<CredentialHttp>();
auth->addRealm(authStore, credApi, "api");
...
}
In the controllers
Let’s assume you have an admin area below /admin
and API routes below /api
. Your admin area login form is at /admin/login
.
In your admin controller header admin.h:
#ifndef ADMIN_H
#define ADMIN_H
#include <Cutelyst/Controller>
class Admin : public Controller
{
Q_OBJECT
public:
explicit Admin(
QObject *parent =
nullptr);
~Admin() override = default;
C_ATTR(index, :Path)
void index(Context *c);
C_ATTR(login, :Local)
void login(Context *c);
C_ATTR(logout, :Local)
void logout(Context *c);
private:
C_ATTR(Auto, :Private)
bool Auto(Context *c);
};
#endif
Implementation in admin.cpp:
#include "admin.h"
#include <Cutelyst/Plugins/Authentication/authentication.h>
Admin::Admin(
QObject *parent) : Controller(parent)
{
}
void Admin::index(Context *c)
{
}
void Admin::login(Context *c)
{
const QString username = c->request()->bodyParam(
"username");
const QString password = c->request()->bodyParam(
"password");
if (Authentication::authenticate(c, { {"username", username}, {"password", password} }, "website")) {
c->response()->redirect(c->uriFor(c->controller("Admin")->actionFor("index")));
return;
} else {
c->setStash("error_msg", "Bad username or password.");
}
} else if (!Authentication::userInRealm(c, "website")) {
c->setStash("error_msg", "Empty username or password.");
}
c->setStash("template", "login.html");
}
void Admin::logout(Context *c)
{
Authentication::logout(c);
c->response()->redirect(c->uriFor("/"));
}
bool Admin::Auto(Context *c)
{
if (c->action() == c->controller("Admin")->actionFor("login")) {
return true;
}
if (!Authentication::userInRealm(c, "website")) {
c->response()->redirect(c->uriFor("/admin/login"));
return false;
}
return true;
}
bool isNull() const const
Our API root controller api.h:
#ifndef API_H
#define API_H
#include <Cutelyst/Controller>
class Api : public Controller
{
... other parts
private:
C_ATTR(Auto, :Private)
bool Auto(Context *c);
}
#endif
Implementation in api.cpp:
#include "api.h"
#include <Cutelyst/Plugins/Authentication/authentication.h>
bool Api::Auto(Context *c)
{
}
static bool authenticate(Context *c, const ParamsMultiMap &userinfo, const QString &realm=QLatin1String(defaultRealm))