Authentication Flow: C++ Library

This guide provides detailed instructions for using the Authentication Flow on a server with our C++ library.

Table of content

This library is still in it’s early stages; Below is an excerpt:

TO USE THIS LIBRARY, REQUEST ACCESS FROM US

1.0 Register the server or application

To register a new server, you need to have px-device-identity. To install it:

  • make sure you have at least Python 3.10
  • are logged in as root
pip3 install https://source.pantherx.org/px-device-identity_latest.tgz

# on some systems, you need to add a flag --break-system-packages, or install in a venv
# pip3 install https://source.pantherx.org/px-device-identity_latest.tgz --break-system-packages

After everything is installed, register the server.

  • Adjust “ServerName” easy to recognize
  • Adjust “Bangkok” to the server location; for ex. City
px-device-identity -o INIT -a https://idp-server.ones-now.com -dn onesid1.com -t "ServerName" -l "Bangkok" -r SERVER

Someone from the OnesID team will need to approve this request this request within 300s.

Schedule a time

2.0 Device Config

#include <QCoreApplication>
#include <QCommandLineParser>
#include <QDebug>
#include "device_auth.hpp"
#include "device_config.hpp"

// Load device config
DeviceConfig deviceConfig = DeviceConfig::loadFromFile("/etc/px-device-identity/device.yml");

2.1 Get Device Access Token

// Reuse the `deviceConfig` from the previous example
DeviceConfig deviceConfig = DeviceConfig::loadFromFile("/etc/px-device-identity/device.yml");
QString deviceClientId = deviceConfig.clientId.toString(QUuid::WithoutBraces);
QString deviceId = deviceConfig.id.toString(QUuid::WithoutBraces);
QString issuerUrl = deviceConfig.host + "/oidc";

// Get device access token
DeviceAccessTokenResponse tokenResponse = DeviceAuth::requestDeviceAccessToken(
    issuerUrl,
    deviceId,
    deviceClientId
);

2.2 Token Manager Registry

The token manager registry keeps track of token manager, which in turn, keep track of token expiry.

Below is an example of how to create a token manager for device authentication:

// Tokenn manager registry
auto& registry = onesoidc::TokenManagerRegistry::instance();

// Device token manager
auto deviceTokenManager = registry.createManager(
    "device",  // Named instance for device authentication
    issuerUrl,
    deviceId,
    deviceClientId,
    onesoidc::DeviceRefreshFunc{[](const QString& issuerUrl, 
            const QString& deviceId, 
            const QString& deviceClientId) -> onesoidc::TokenResponse {
        return onesoidc::TokenResponse(
            onesoidc::DeviceAuth::requestDeviceAccessToken(
                issuerUrl, deviceId, deviceClientId));
    }}
);

// Connect signals for device token manager
QObject::connect(deviceTokenManager.get(), &onesoidc::TokenManager::tokensRefreshed,
    [](const onesoidc::TokenResponse& tokens) {
        onesoidc::LOG_DEBUG("Device token available/refreshed:" + tokens.accessToken);
    });

// Store device access token
deviceTokenManager->storeTokens(onesoidc::TokenResponse(tokenResponse));

3.0 CIBA

Authenticate with user username (OnesID).

3.1.1 Make CIBA Request

QString deviceJwt = JWT::createDeviceJWT(deviceId, deviceClientId, issuerUrl);
QString deviceJwtCiba = JWT::createDeviceJWT(
    deviceId,
    deviceClientId, 
    issuerUrl,
    username,
    QString("Authorize login to Example.com?"),
    QString("openid offline_access profil"),
    "https://example.com"
);

CibaResponse cibaResponse = CibaAuth::cibaRequest(
    issuerUrl,
    deviceClientId,
    deviceJwt,
    deviceJwtCiba
);

qDebug() << "CIBA request successful. Auth Request ID:" << cibaResponse.authReqId;

3.1.2 Check CIBA Status

while (true) {
    try {
        CibaStatusResponse status = CibaAuth::checkCibaStatus(
            issuerUrl,
            cibaResponse.authReqId,
            deviceId,
            deviceClientId
        );
        
        qDebug() << "Authentication successful!";
        qDebug() << "Access Token:" << status.accessToken;
        break;
        
    } catch (const std::runtime_error& e) {
        if (QString(e.what()).contains("authorization_pending")) {
            qDebug() << "Waiting for authorization...";
            QThread::sleep(5);
            continue;
        }
        throw;
    }
}

Validate token:

# TODO

3.2.1 Signals: Make CIBA Request and Check Status

Below example is a little more comprehensive

  1. Initiate CIBA instance
  2. Setup token manaer
  3. Connect signals
  4. Start authentication
// Initialize singleton
onesoidc::CibaAuthSignals::instance().initialize(issuerUrl, 
    config.id.toString(QUuid::WithoutBraces),
    config.clientId.toString(QUuid::WithoutBraces),
    targetServerUrl);

auto& auth = onesoidc::CibaAuthSignals::instance();

// Token manager
auto tokenManager = registry.createManager(
    "ciba_user",  // Named instance for user authentication
    issuerUrl,
    deviceId,
    deviceClientId,
    onesoidc::CibaRefreshFunc{[&auth](const QString& issuerUrl, const QString& refreshToken, 
            const QString& deviceId, const QString& deviceClientId) -> onesoidc::TokenResponse {
        onesoidc::TokenResponse response;
        QObject::connect(&auth, &onesoidc::CibaAuthSignals::tokenRefreshed,
            [&response](const onesoidc::CibaStatusResponse& status) {
                response = onesoidc::TokenResponse(status);
            });
        auth.refreshToken(refreshToken);
        return response;
    }}
);

// Connect signals
QObject::connect(tokenManager.get(), &onesoidc::TokenManager::tokensRefreshed,
    [](const onesoidc::TokenResponse& tokens) {
        onesoidc::LOG_DEBUG("Token available/refreshed:" + tokens.accessToken);
        onesoidc::LOG_DEBUG("ID Token:" + tokens.idToken.value_or(""));
    });

QObject::connect(&auth, &onesoidc::CibaAuthSignals::authenticated,
    [&tokenManager, targetServerUrl](const onesoidc::CibaStatusResponse& status) {
        onesoidc::LOG_DEBUG("Authenticated:" + status.accessToken);
        tokenManager->storeTokens(onesoidc::TokenResponse(status));
        onesoidc::LOG_DEBUG("Tokens stored successfully");

        tokenManager->forceRenewToken();
    });

QObject::connect(&auth, &onesoidc::CibaAuthSignals::tokenRefreshed,
    [](const onesoidc::CibaStatusResponse& status) {
        onesoidc::LOG_DEBUG("Token refreshed successfully:" + status.accessToken);
    });

QObject::connect(&auth, &onesoidc::CibaAuthSignals::finished,
    &app, &QCoreApplication::quit);

// Error handling
QObject::connect(&auth, &onesoidc::CibaAuthSignals::error,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Error occurred:" + error);
        QCoreApplication::exit(1);
    });

QObject::connect(&auth, &onesoidc::CibaAuthSignals::networkError,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Network error:" + error);
    });

QObject::connect(&auth, &onesoidc::CibaAuthSignals::authenticationError,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Authentication error:" + error);
    });
    
// Start authentication
auth.startAuth(parser.value(usernameOption));

4.0 QR

4.1 QR Authentication

// Reuse the `deviceConfig` from the previous example
// Get device access token
DeviceAccessTokenResponse tokenResponse = DeviceAuth::requestDeviceAccessToken(...)

// Create QR session
QRAuthSession session = QRAuth::createQRSession(issuerUrl, tokenResponse.accessToken);

// Display QR code
qDebug() << "Scan this QR code:";
qDebug().noquote() << QRAuth::generateQRCode(session);

// Poll for session status
while (true) {
    QThread::sleep(5);
    
    session = QRAuth::verifyQRSession(
        issuerUrl,
        tokenResponse.accessToken,
        session.sessionId
    );
    
    if (!session.loginHintToken.isEmpty()) {
        qDebug() << "QR code scanned! Proceeding with CIBA authentication...";
        qDebug() << "Login hint token:" << session.loginHintToken;
        break;
    }
    
    qDebug() << "Waiting for QR code scan...";
}

// Proceed with CIBA
QString deviceJwt = JWT::createDeviceJWT(deviceId, deviceClientId, issuerUrl);
LoginHint loginHint = LoginHint(
    session.loginHintToken,
    LoginHintKind::LoginHintToken
);
QString deviceJwtCiba = JWT::createDeviceJWT(
    deviceId,
    deviceClientId, 
    issuerUrl,
    loginHint,
    "Authenticate with device?",
    "openid",
    targetServerUrl,
    session.sessionId
);

CibaResponse cibaResponse = CibaAuth::cibaRequest(
    issuerUrl,
    deviceClientId,
    deviceJwt,
    deviceJwtCiba
);

qDebug() << "CIBA request initiated. Waiting for authorization...";

4.2 Signals: QR Authentication

Below example is a little more comprehensive

  1. Initiate QR instance
  2. Connect signals
  3. Start authentication

Refer to 3.2.1 Signals: Make CIBA Request and Check Status for an example on how-to setup the token manager.

// Initialize singleton
onesoidc::QRAuthSignals::instance().initialize(issuerUrl, deviceId, deviceClientId, vhhServerUrl);
auto& qrAuth = onesoidc::QRAuthSignals::instance();

// Connect signals
QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::qrSessionCreated, 
    [](const onesoidc::QRAuthSession& session) {
        onesoidc::LOG_INFO("Scan this QR code:");
        onesoidc::LOG_INFO("Session ID: " + session.sessionId);
        onesoidc::LOG_INFO("CB URL: " + session.cbUrl);
        qDebug().noquote() << onesoidc::QRAuth::generateASCIIQRCode(session);
    });


// Authentication signals
QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::waitingForAuthorization,
    []() {
        onesoidc::LOG_DEBUG("Waiting for user authorization...");
    });

QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::authenticated,
    [](const onesoidc::CibaStatusResponse& status) {
        onesoidc::LOG_DEBUG("Authentication successful!");
        onesoidc::LOG_DEBUG("Access Token: " + status.accessToken);
        
        if (status.refreshToken.has_value()) {
            onesoidc::LOG_DEBUG("Refresh token available: " + status.refreshToken.value());
        }
    });

// Error handling
QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::error,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Error occurred:" + error);
        QCoreApplication::exit(1);
    });

QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::networkError,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Network error:" + error);
    });

QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::authenticationError,
    [](const QString& error) {
        onesoidc::LOG_ERROR("Authentication error:" + error);
    });

QObject::connect(&qrAuth, &onesoidc::QRAuthSignals::finished, 
    &app, &QCoreApplication::quit);

// Start authentication
qrAuth.startQRAuthentication(deviceAccessToken);

WIP

6.0 Well Known

To determine the URL of an application known to IDP by identifier, you can fetch well known, and use it to create a JWT targeting the application, for the CIBA flow.

vhhServer = ApplicationsWellKnown::getWellKnownApplicationByClientIdentifier(
    issuerUrl,
    tokenResponse.accessToken,
    "vhh-server"
);

auto targetServerUrl = vhhServer.getUrl(issuerUrl);

QString deviceJwt = JWT::createDeviceJWT(deviceId, deviceClientId, issuerUrl);
LoginHint loginHint = LoginHint(
    username,
    LoginHintKind::LoginHint
);
QString deviceJwtCiba = JWT::createDeviceJWT(
    deviceId,
    deviceClientId, 
    issuerUrl,
    loginHint,
    QString("Authorize login to Example.com?"),
    QString("openid offline_access profil"),
    targetServerUrl
);

Contact

Found a problem, or have a question related to Authentication Flow: C++ Library?

ONES Now Documentation

© 2025 ONES Now Documentation | Author Franz Geffke