Token-Based Authetification in Autonomous Systems via Qt6 using Qr codes. QR codes


Content

Qt codes

To generate and display Qr-codes, we will use the library qt-qrcode. This library is based, like most Qr code libraries, on libqrencode. But the problem with most libraries is the disgusting quality of the code. But this lib looks pretty decent.

Library support ended 8 years ago, so I did fork. Now from the changes there is a bug fix (if you just create a qr-code through the constructor, it will be invalid, before you had to fill it through setters), and pro / pri-files have been changed so that you can connect the library to your project without unnecessary gestures and how dll(so), and just as source files.

About the application

The application itself will be executed as a token + expiration list. There will be functions to create a new token, delete the old one, update the entire list and show the selected token as a QR code.

List of tokens

Let’s create a ListModel to store the list of tokens. The model will itself go to the address specified in the constructor, getting, creating or deleting tokens.

tokenmodel
TokenModel.h
class TokenModel: public QAbstractListModel {
private:
    QPointer<QNetworkAccessManager> m_manager;
    QMap<QUuid, QDateTime> m_tokens;
    const QString m_tokenApi;
public:
    TokenModel(const QString& address, QObject* parent = nullptr);
public:
    QPair<QUuid, QDateTime> tokenAt(qint32 index) const;
public:
    void create(qint64 expirationSpan);
    void remove(const QUuid& token);
    void refresh();
public:
    int rowCount(const QModelIndex &parent) const;
    QVariant data(const QModelIndex &index, int role) const;
private:
    void parseTokensFromJson(const QJsonDocument& document);
};
TokenModel.cpp
TokenModel::TokenModel(const QString& address, QObject* parent)
    :QAbstractListModel{ parent },
     m_manager{ new QNetworkAccessManager{ this } },
     m_tokenApi{ "http://" + address + "/auth/token" } {}
QPair<QUuid, QDateTime> TokenModel::tokenAt(qint32 index) const {
    const auto token = std::next(m_tokens.begin(), index);
    return { token.key(), token.value() };
}

void TokenModel::create(qint64 expirationSpan) {
    const auto request = QNetworkRequest{ QString{ "%1/%2/%3" }.arg(m_tokenApi).arg("create").arg(expirationSpan) };
    const auto reply = m_manager->get(request);
    connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);
    connect(reply, &QNetworkReply::finished, this, &TokenModel::refresh);
}
void TokenModel::remove(const QUuid &token) {
    const auto request = QNetworkRequest{ QString{ "%1/%2/%3" }.arg(m_tokenApi).arg("remove").arg(token.toString(QUuid::StringFormat::WithoutBraces)) };
    const auto reply = m_manager->get(request);
    connect(reply, &QNetworkReply::finished, reply, &QObject::deleteLater);
    connect(reply, &QNetworkReply::finished, this, &TokenModel::refresh);
}
void TokenModel::refresh() {
    const auto reply = m_manager->get(QNetworkRequest{ QString{ "%1/%2" }.arg(m_tokenApi).arg("all") });
    QObject::connect(reply, &QNetworkReply::finished, [this, reply]{
        const auto result = QJsonDocument::fromJson(reply->readAll());
        this->parseTokensFromJson(result);
        reply->deleteLater();
    });
}

int TokenModel::rowCount(const QModelIndex&/*parent*/) const {
    return m_tokens.size();
}
QVariant TokenModel::data(const QModelIndex &index, int role) const {
    if(not index.isValid()) return {};
    if(role != Qt::ItemDataRole::DisplayRole) return {};

    const auto item = std::next(m_tokens.begin(), index.row());
    return QString{ "[%1 <=> %2]" }.arg(item.key().toString(), item.value().toString());
}

void TokenModel::parseTokensFromJson(const QJsonDocument& document) {
    this->beginResetModel();
    m_tokens.clear();
    const auto tokens = document.object();
    for(const auto& token: tokens.keys())
        m_tokens.insert(QUuid::fromString(token), QDateTime::fromSecsSinceEpoch(tokens[token].toInteger()));

    this->endResetModel();
}

QR code display

Let’s write two classes for display: QrcodeWidget And QrCodeDialog. The first one will just draw the qr code on itself and the second one will use the first one to display the qr code in the dialog box.

Creating classes is trivial. The qr-code itself is drawn by means of the class QtQrCodePainter from the qt-qrcode library. This class can draw qr-code on any QPainterDevice, just push QPainterDevice into QPaint, and pass QtQrCodePainter.

QrcodeWidget
class QrCodeWidget: public QWidget {
private:
    QtQrCode m_code;
public:
    QrCodeWidget(const QtQrCode &code = {}, QWidget *parent = nullptr);
protected:
    virtual void paintEvent(QPaintEvent *event) override;
};

QrCodeWidget::QrCodeWidget(const QtQrCode &code, QWidget *parent)
    :QWidget{ parent }, m_code{ code } {}

void QrCodeWidget::paintEvent(QPaintEvent *event) {
    QWidget::paintEvent(event);
    QPainter painter{ this };
    QtQrCodePainter{}.paint(m_code, painter, width(), height());
}
QrCodeDialog
class QrCodeDialog : public QDialog {
public:
    QrCodeDialog(const QByteArray &data, QWidget* parent = nullptr);
};

QrCodeDialog::QrCodeDialog(const QByteArray &data, QWidget* parent)
    :QDialog{ parent } {
    auto codeView = new QrCodeWidget{ QtQrCode{ data } };
    auto layout = new QVBoxLayout{};
    layout->addWidget(codeView);
    this->setLayout(layout);
}

Main application window

The main window will contain 4 buttons for updating the list, deleting, adding and displaying tokens.

You can determine which code to remove or display using the selection model (QAbstractSelectionModel). This model is provided by the display classes (and can be replaced by any other), such as QListView.

TokenView.h
class TokenView: public QWidget {
private:
    QPointer<TokenModel> m_tokens;
    QPointer<QListView> m_view;
public:
    TokenView(QWidget* parent = nullptr);
private slots:
    void showToken();
    void createToken();
    void removeToken();
};

The constructor creates 4 buttons, a model and views, sets connections (connect) for operations and scatters elements across layouts.

Icons taken from the site https://icons8.com.

TokenView constructor
TokenView::TokenView(QWidget* parent)
    :QWidget{ parent },
     m_tokens{ new TokenModel{ "127.0.0.1:5555" } },
     m_view{ new QListView{} } {
        m_view->setModel(m_tokens);
        auto showTokenButton = new QPushButton{ QIcon{ ":/images/icon-qr-code.png" }, {} };
        auto refreshModelButton = new QPushButton{ QIcon{ ":/images/icon-update.png" }, {} };
        auto createTokenButton = new QPushButton{ QIcon{ ":/images/icon-plus.png" }, {} };
        auto removeTokenButton = new QPushButton{ QIcon{ ":/images/icon-minus.png" }, {} };

        connect(m_view, &QAbstractItemView::doubleClicked, this, &TokenView::showToken);
        connect(showTokenButton, &QPushButton::clicked, this, &TokenView::showToken);
        connect(refreshModelButton, &QPushButton::clicked, m_tokens, &TokenModel::refresh);
        connect(createTokenButton, &QPushButton::clicked, this, &TokenView::createToken);
        connect(removeTokenButton, &QPushButton::clicked, this, &TokenView::removeToken);

        auto layout = new QVBoxLayout{};
            auto buttonLayout = new QHBoxLayout{};
            buttonLayout->addWidget(refreshModelButton);
            buttonLayout->addWidget(createTokenButton);
            buttonLayout->addWidget(removeTokenButton);
            buttonLayout->addWidget(showTokenButton);
            buttonLayout->addStretch(1);
        layout->addLayout(buttonLayout, 0);
        layout->addWidget(m_view, 1);
        this->setLayout(layout);
        m_tokens->refresh();
    }

Updating the list is called directly on the model when the button is clicked, and the rest of the operations need a little more logic, so separate methods are created for them.

The QR code is displayed in a dialog box expanded to the maximum size. You can close it with a cross, or by pressing Escape.

When creating a token, its lifetime is requested in seconds, and when deleting, it is only necessary that there be one allocated token.

Token View Operations
void TokenView::showToken() {
    const auto selection = m_view->selectionModel()->selection();
    if(selection.size() != 1)
        return;

    const auto token = m_tokens->tokenAt(selection.indexes().front().row());
    QrCodeDialog qrCoreView{ token.first.toRfc4122(), this };
    qrCoreView.showMaximized();
    qrCoreView.exec();
}
void TokenView::createToken() {
    bool isValidExpirationSpan{};
    const auto expirationSpan = QInputDialog::getInt(this, "Expiration span", "Seconds:", 86400,
        0, std::numeric_limits<qint32>::max(), 1, &isValidExpirationSpan);
    if(not isValidExpirationSpan)
        return;

    m_tokens->create(expirationSpan);
}
void TokenView::removeToken() {
    const auto selection = m_view->selectionModel()->selection();
    if(selection.size() != 1)
        return;

    const auto token = m_tokens->tokenAt(selection.indexes().front().row());
    m_tokens->remove(token.first);
}
List of tokens

List of tokens

QR code

QR code

A bit of styling

Windows default windows are pretty crazy (that’s why I like distributions like Manjaro Linux, where even unstyled apps look pretty nice).

Well, on Windows, we can add a set of qss files at any time. For simplicity, we use the tool qt_material. With it, you can generate qss files with a bunch of settings right under (well, sort of) all widgets.

For this we do:

pip install qt_material

Then we go to the folder where we want to generate styles, open the python console in it and drive in, for example, such settings (more details on project website):

qt_material settings example
from qt_material import export_theme

extra = {

    # Button colors
    'danger': '#dc3545',
    'warning': '#ffc107',
    'success': '#17a2b8',

    # Font
    'font_family': 'monospace',
    'font_size': '14px',
    'line_height': '13px',

    # Density Scale
    'density_scale': '0',

    # environ
    'pyside6': True,
    'linux': True,

}

export_theme(
    theme="dark_teal.xml", qss="dark_teal.qss", rcc="styles.qrc",
    output="theme", prefix='icon:/', invert_secondary=False, extra=extra)

As a result, we get files with resources that need to be connected to our application (you can see how the pro file will look like on GitHub). It is also worth noting here that image files (icons, etc.) will be included as DISTFILES, but for distribution it may be convenient to include them as RESOURCES.

Now we set the generated style for the application:

main
int main(int argc, char *argv[]) {
    QApplication app{ argc, argv };
    {
        QDir::addSearchPath("icon", ":/icon/theme");
        QFile file(":/file/dark_teal.qss");
        file.open(QFile::ReadOnly);
        QString styleSheet = QLatin1String(file.readAll());
        app.setStyleSheet(styleSheet);
    }

    TokenView window{};
    window.show();
    return app.exec();
}

And it starts to look something like this:

Main window

Main window

Dialogue for entering a number

Dialogue for entering a number

QR code

QR code

Conclusion

In a real project, some personal data should be used to generate a token, so that in the event of a token leak, it is clear to whom to give the cap. And, of course, there should not be any display of tokens in the form of a list, this is done simply for ease of perception.

If we can get our hands on it, let’s see how we can make Android applications in Qt to scan such a token and use it in requests.

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *