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.
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
- Initiate CIBA instance
- Setup token manaer
- Connect signals
- 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
- Initiate QR instance
- Connect signals
- 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);
5.0 Consent API
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
);