QR Code Device Synchronization
Relevant source files
The following files were used as context for generating this wiki page:The QR Code Device Synchronization feature provides a secure mechanism for authenticating a session on a primary device (e.g., a laptop) by using a secondary device (e.g., a mobile phone) as a key. The user scans a time-sensitive QR code displayed on the laptop with their phone. This action verifies the session on the backend, which is then detected by the laptop, automatically granting access to the attendance page without requiring a password.
This system is designed to bridge the gap between a stationary attendance kiosk (the laptop) and a user's personal device, enabling a quick and seamless authentication flow. It leverages a combination of server-side session management, client-side polling, and unique, expiring codes to ensure security and usability.
Architecture Overview
The system consists of three main parts: the Backend Server (Flask), the Primary Device UI (Laptop Browser), and the Secondary Device UI (Mobile Browser). They interact to synchronize the authentication state.
The following diagram illustrates the high-level architecture of the components involved in the QR sync process.
Sources: app.py, qr_sync.py, templates/qr_auth.html
Core Components
The synchronization logic is managed by several key components on the backend and frontend.
Backend Components (qr_sync.py)
The backend logic is primarily handled by two singleton classes that manage the state of QR sessions and device-employee associations.
QRSyncManager
This class manages the lifecycle of QR code sessions. It is a thread-safe, in-memory dictionary that stores session data.
Diagram illustrating the methods of the QRSyncManager class.
Sources: qr_sync.py:91-209
A background thread periodically calls cleanup_old_sessions() to remove sessions older than 5 minutes, ensuring the sessions dictionary does not grow indefinitely.
Sources: qr_sync.py:212-221
Session Data Structure
Each session stored in QRSyncManager has the following structure:
| Key | Type | Description |
|---|---|---|
verified | bool | True if the QR code has been scanned by a mobile device. |
timestamp | datetime | The time the session was created. |
code | string | The 8-character unique unit code displayed to the user. |
verified_at | datetime | The time the session was verified (scanned). |
device_info | string | Information about the scanning device (e.g., 'mobile'). |
employee_info | dict | Information about the recognized employee, if any. |
device_id | string | The unique ID of the scanning mobile device. |
Sources: qr_sync.py:97-103, qr_sync.py:186-194
DeviceRegistry
This class maintains a persistent mapping between a mobile device's unique ID and an employee record. This allows the system to recognize a user's phone on subsequent scans and provide a personalized welcome message. The data is stored in data/device_registry.json.
Diagram illustrating the methods of the DeviceRegistry class.
Sources: qr_sync.py:12-88
Device Data Structure
| Key | Type | Description |
|---|---|---|
employee_id | int | The ID of the employee associated with the device. |
employee_name | string | The name of the employee. |
nik | string | The NIK of the employee. |
registered_at | string | ISO format timestamp of when the device was first registered. |
last_seen | string | ISO format timestamp of the last time the device was used. |
Sources: qr_sync.py:46-52
Frontend Component (qr_auth.html)
The primary device UI (qr_auth.html) is responsible for displaying the QR code and continuously polling the backend to check if the code has been scanned.
The core of the frontend logic is the checkQRSyncStatus JavaScript function, which is executed every 5 seconds.
// templates/qr_auth.html:631-654
function checkQRSyncStatus() {
syncCheckCount++;
if (syncCheckCount > MAX_SYNC_CHECKS) {
// ... stop polling ...
return;
}
fetch('/api/qr_sync_status')
.then(response => response.json())
.then(data => {
console.log(`📊 QR Sync Check ${syncCheckCount}:`, data);
if (data.success && data.has_pending_sync) {
// ...
handlePendingQRSync(data);
}
})
.catch(error => console.error('❌ QR sync check error:', error));
}
Sources: templates/qr_auth.html:631-654
When has_pending_sync is true, it triggers handlePendingQRSync, which in turn calls acceptQRSync to finalize the process and redirect the page.
Sources: templates/qr_auth.html:656-692
End-to-End Synchronization Flow
The synchronization process involves a coordinated sequence of requests between the laptop, the mobile phone, and the server.
This sequence diagram details the entire flow from the initial page load on the laptop to the final redirection to the attendance page.
QR Code and Unit Code Lifecycle
- Generation: A unique 8-character
unit_codeis generated using an MD5 hash of a secret string combined with a time slot. The time slot is based on the current timestamp divided by the validity period (10 minutes), ensuring the same code is generated within the same 10-minute window. A forced refresh adds a counter to the hash to guarantee a new code.
Sources: app.py:68-81 - Validity: The QR code and its associated
unit_codeare valid for 10 minutes. After this period, a new code is generated automatically on the next request or page refresh.
Sources: app.py:64, app.py:83-93 - Refresh: The QR code can be refreshed in two ways:
- Automatically: The countdown timer on
qr_auth.htmltriggers a refresh when it reaches zero. - Manually: The user can click the "Refresh QR" button.
Both actions call the/api/refresh_qrendpoint, which forces the generation of a newunit_code.
Sources: templates/qr_auth.html:565-596, app.py:768-798
- Automatically: The countdown timer on
Device Recognition
The system can recognize a user's mobile device to provide a personalized experience.
- Device ID Generation: When a user first scans a QR code, the
mobile_verify.htmlpage's JavaScript checks for aabsenn_device_idin the phone'slocalStorage. If it doesn't exist, a new unique ID is generated and stored.
Sources: templates/mobile_verify.html:105-115 - Verification Check: This
device_idis passed as a query parameter to the/verifyendpoint. The backend usesdevice_registry.get_employee_by_device(device_id)to check if the device is already associated with an employee.
Sources: app.py:840-845, qr_sync.py:64-71 - Personalized Welcome: If the device is recognized, the
employee_infois passed to theqr_scan_success.htmltemplate, which displays a "Selamat Datang, [Nama Karyawan]!" message.
Sources: app.py:853-858, templates/qr_scan_success.html:167-183 - Device Registration: If a device is not yet recognized, it gets registered automatically after the user completes their first successful attendance using that device. The
update_attendancefunction inapp.pycallsdevice_registry.register_device(...).
Sources: app.py:1838-1859, qr_sync.py:44-57
API Endpoints
The following API endpoints are central to the QR synchronization feature.
| Method | Endpoint | Description | Source File |
|---|---|---|---|
GET | /auth | Renders the main QR authentication page for the laptop. | app.py:533 |
GET | /mobile_verify | Intermediate page for mobile devices to capture device_id. | app.py:829 |
GET | /verify | Verifies the unit_code from the mobile device and marks the session as authenticated on the backend. | app.py:838 |
GET | /api/qr_sync_status | Polling endpoint for the laptop to check if a QR scan has occurred. | app.py:548, static/openapi.yaml:50 |
POST | /api/accept_qr_sync | Called by the laptop to consume the verified session and gain access to the attendance page. | app.py:695, static/openapi.yaml:70 |
GET | /api/refresh_qr | Generates a new QR code and unit_code. | app.py:768, static/openapi.yaml:94 |
POST | /api/clear_qr_session | Clears the verification flag from the laptop's session after attendance is complete to prevent re-entry. | app.py:660, static/openapi.yaml:113 |
Conclusion
The QR Code Device Synchronization system is a robust and user-friendly feature that enhances the security and convenience of the attendance process. By using a mobile device as a physical key, it eliminates the need for passwords on the attendance kiosk. The combination of time-limited codes, server-side state management via QRSyncManager, and client-side polling creates a seamless cross-device experience. The addition of DeviceRegistry further improves the user experience by personalizing the interaction for returning users.