Overview
Salud uses QR codes as a convenient and secure method to share medical records with healthcare providers. QR codes encode all necessary information for access verification and record decryption.
QR codes contain encrypted data and access tokens - they are safe to display on your phone screen during appointments.
QR Code Data Structure
Each QR code contains the following JSON data:
interface QRCodeData {
version : number // Protocol version (currently 1)
accessToken : string // Cryptographic access token
recordId : string // Unique record identifier
patientAddress : string // Your Aleo wallet address
expiresAt : number // Unix timestamp of expiration
encryptedViewKey ?: string // Encrypted with doctor's public key
encryptedData ?: string // The encrypted record data
}
Version Control Protocol version ensures compatibility across updates
Access Token Cryptographic proof verified on blockchain
Encrypted View Key Allows doctor to decrypt the medical data
Expiration Built-in time limit prevents stale access
Generating QR Codes
Step-by-Step Process
Select Record
From the Records page, click on the record you want to share
Click Share
Click the “Share” button to open the share modal
Configure Access
Enter doctor’s Aleo address (optional)
Select access duration (1 hour to 7 days)
Generate QR Code
Click “Generate QR Code” to create the shareable QR code
Share with Doctor
Show the QR code on your screen or download it as an image
QR Code Generation Example
Here’s how QR codes are generated in the application:
// From ShareRecordModal.tsx:175-185
const qrData : QRCodeData = {
version: 1 ,
accessToken ,
recordId: record . recordId ,
patientAddress: user . address ,
expiresAt: expiresAt ?. getTime () || 0 ,
encryptedViewKey ,
encryptedData: record . data ,
};
// Generate QR code from JSON data
< QRCodeSVG
value = {JSON.stringify(qrData)}
size = { 200 }
level = "H" // High error correction
includeMargin
/>
QR codes use high error correction (Level H) to ensure they remain scannable even if partially damaged or obscured.
QR Code Features
Visual Elements
200x200 pixels Standard size optimized for mobile scanning
High Error Correction Can recover from 30% data loss or damage
Margin Included Built-in quiet zone for reliable scanning
Optional Logo Can embed Salud logo in center
Live Countdown Timer
QR codes display a real-time countdown showing remaining access time:
// From ShareRecordModal.tsx:72-99
useEffect (() => {
if ( ! expiresAt ) return ;
const updateCountdown = () => {
const now = new Date ();
const diff = expiresAt . getTime () - now . getTime ();
if ( diff <= 0 ) {
setCountdown ( 'Expired' );
return ;
}
const hours = Math . floor ( diff / ( 1000 * 60 * 60 ));
const minutes = Math . floor (( diff % ( 1000 * 60 * 60 )) / ( 1000 * 60 ));
const seconds = Math . floor (( diff % ( 1000 * 60 )) / 1000 );
if ( hours > 0 ) {
setCountdown ( ` ${ hours } h ${ minutes } m ${ seconds } s` );
} else if ( minutes > 0 ) {
setCountdown ( ` ${ minutes } m ${ seconds } s` );
} else {
setCountdown ( ` ${ seconds } s` );
}
};
updateCountdown ();
const interval = setInterval ( updateCountdown , 1000 );
return () => clearInterval ( interval );
}, [ expiresAt ]);
Scanning QR Codes
Doctor Portal Scanner
Healthcare providers use the Doctor Portal to scan patient QR codes:
Navigate to Doctor Portal
Click “Doctor Portal” in the main navigation
Connect Wallet
Doctor must connect their Aleo wallet first
Start Scanner
Click “Start Scanning” to activate the camera
Scan QR Code
Position the patient’s QR code within the scanner frame
View Record
Access is verified on blockchain and record is displayed
Scanner Implementation
The scanner uses the html5-qrcode library for camera access:
// From DoctorQRScanner.tsx:53-79
const startScanner = async () => {
setError ( null );
setScanStatus ( 'scanning' );
try {
const scanner = new Html5Qrcode ( 'qr-reader' );
scannerRef . current = scanner ;
await scanner . start (
{ facingMode: 'environment' }, // Use back camera
{
fps: 10 , // 10 frames per second
qrbox: { width: 250 , height: 250 }, // Scan area size
aspectRatio: 1 , // Square aspect ratio
},
onScanSuccess ,
onScanFailure
);
setCameraPermission ( true );
} catch ( err ) {
console . error ( 'Scanner error:' , err );
setCameraPermission ( false );
setError ( 'Unable to access camera. Please grant camera permissions.' );
setScanStatus ( 'idle' );
}
};
Camera permissions must be granted for QR code scanning. The browser will prompt for permission on first use.
Scan Verification Process
When a QR code is scanned, the system performs several checks:
The QR code JSON is parsed and validated for required fields const data : QRCodeData = JSON . parse ( decodedText );
if ( ! data . accessToken || ! data . recordId || ! data . patientAddress ) {
throw new Error ( 'Invalid QR code format' );
}
Verify the access hasn’t expired based on the embedded timestamp if ( data . expiresAt < Date . now ()) {
throw new Error ( 'This access token has expired' );
}
The access token is verified against the Aleo blockchain using verify_access
Doctor’s private key decrypts the view key from the QR code const viewKey = decryptWithPrivateKey (
data . encryptedViewKey ,
doctorAddress
);
The decrypted view key is used to access the medical record
QR Code Actions
Copy Access Token
The access token can be copied as text:
// From ShareRecordModal.tsx:143-150
const handleCopyToken = async () => {
if ( ! accessToken ) return ;
const success = await copyToClipboard ( accessToken );
if ( success ) {
setCopied ( true );
setTimeout (() => setCopied ( false ), 2000 );
}
};
Download QR Code
QR codes can be downloaded as PNG images:
// From ShareRecordModal.tsx:152-173
const handleDownloadQR = () => {
const svg = document . getElementById ( 'qr-code-svg' );
if ( ! svg ) return ;
const svgData = new XMLSerializer (). serializeToString ( svg );
const canvas = document . createElement ( 'canvas' );
const ctx = canvas . getContext ( '2d' );
const img = new Image ();
img . onload = () => {
canvas . width = img . width ;
canvas . height = img . height ;
ctx ?. drawImage ( img , 0 , 0 );
const pngFile = canvas . toDataURL ( 'image/png' );
const downloadLink = document . createElement ( 'a' );
downloadLink . download = `salud-qr- ${ title } .png` ;
downloadLink . href = pngFile ;
downloadLink . click ();
};
img . src = 'data:image/svg+xml;base64,' + btoa ( svgData );
};
Downloaded QR codes can be printed or sent electronically to the healthcare provider ahead of appointments.
Scanner States
The QR scanner has five different states:
Idle Scanner is ready to start - shows “Start Scanning” button
Scanning Camera is active and looking for QR codes
Verifying QR code found, verifying access on blockchain
Success Access verified, displaying medical record
Error Verification failed or QR code invalid
Security Considerations
QR Code Safety
Time-Limited QR codes automatically expire after the specified duration
One-Time Use Each QR code contains a unique access token
Blockchain Verified Access verification happens on-chain, can’t be forged
Encrypted Data Medical data in QR code is encrypted, requires view key
Best Practices
Display Only When Needed
Don’t leave QR codes visible longer than necessary
Verify Doctor First
Confirm you’re sharing with the correct healthcare provider
Use Short Durations
Select the minimum duration needed for the appointment
Delete Downloaded QR Codes
Remove saved QR code images after use
Never share QR code screenshots publicly or on social media. Anyone with the QR code can access the medical record during its valid period.
Doctor View After Scanning
After successful verification, doctors see:
Success Badge : Green confirmation that access is verified
Record Title : Name of the medical record
Record Type : Category badge (Lab Results, Prescription, etc.)
Record Description : Full medical information
Patient Address : Truncated Aleo address of the patient
Expiration Time : When access will expire
Access Token : The cryptographic token used for verification
Privacy Notice : Information about blockchain security
// From DoctorQRScanner.tsx:263-348
{ scanStatus === 'success' && recordData && (
< motion . div className = "space-y-4" >
{ /* Success badge */ }
< div className = "flex items-center justify-center gap-2 rounded-lg bg-success-50 p-4" >
< CheckCircle2 className = "text-success-600" />
< span className = "font-medium text-success-900" >
Access Verified Successfully
</ span >
</ div >
{ /* Record details */ }
< div className = "rounded-xl border border-slate-200 bg-white p-6" >
< div className = "mb-4 flex items-start gap-4" >
< div className = "flex h-12 w-12 shrink-0 items-center justify-center rounded-xl bg-primary-100" >
< FileText className = "text-primary-600" />
</ div >
< div >
< h3 className = "font-semibold text-slate-900" >
{recordData. title }
</ h3 >
< Badge variant = "primary" size = "sm" className = "mt-1" >
{RECORD_TYPES [recordData.recordType].name}
</Badge>
</div>
</div>
<p className="mb-4 text-sm text-slate-600">
{recordData.description}
</p>
{ /* Metadata */ }
<div className="space-y-2 border-t border-slate-100 pt-4">
<div className="flex items-center justify-between text-sm">
<span className="flex items-center gap-2 text-slate-500">
<User size={14} />
Patient
</span>
<span className="font-mono text-xs text-slate-700">
{truncateAddress(recordData.patientAddress, 8, 6)}
</span>
</div>
<div className="flex items-center justify-between text-sm">
<span className="flex items-center gap-2 text-slate-500">
<Clock size={14} />
Access Expires
</span>
<span className="text-slate-700">
{formatDateTime(recordData.expiresAt)}
</span>
</div>
</div>
</div>
</motion.div>
)}
Troubleshooting
Ensure good lighting and steady camera position
Check that camera permissions are granted
Try adjusting distance between camera and QR code
Clean phone camera lens if blurry
Scanner shows 'Invalid QR code'
Verify the QR code is from Salud Health app
Check if the QR code image is corrupted
Ensure the QR code hasn’t expired
Try generating a new QR code
'Access Denied' after scanning
Check if access has been revoked by patient
Verify you’re using the correct Aleo wallet address
Confirm the QR code hasn’t expired
Ensure blockchain connection is active
Grant camera permissions in browser settings
Check that no other app is using the camera
Try a different browser (Chrome/Safari recommended)
Restart the browser and try again
Next Steps
Access Sharing Learn more about access grants and permissions
Privacy & Security Understand the security model