Overview
The verify_access transition allows healthcare providers (or the system) to verify that they have valid access to a specific medical record. This is a verification operation - the transaction succeeds only if all access conditions are met. If any validation fails, the transaction is rejected.
Function Signature
async transition verify_access(
access_token: field,
doctor: address,
record_id: field
) -> Future
Source: main.leo:342-349
Parameters
The cryptographic access token received from the patient’s QR code. This token was generated by the grant_access function and serves as proof of permission.
The healthcare provider’s Aleo address. Must match the address specified when the access was granted. This prevents token sharing between different doctors.
The unique identifier of the medical record being accessed. Must match the record ID specified when the access was granted. This prevents using a token for a different record.
Returns
Async finalization that performs all verification checks. The transaction:
- Succeeds if all validations pass (access is valid)
- Fails if any validation fails (access is denied)
There is no explicit return value - success/failure of the transaction indicates access validity.
Validation Checks
The finalize function performs the following checks in order:
1. Token Exists and Is Valid
let is_valid: bool = access_token_valid.get_or_use(access_token, false);
assert(is_valid);
Fails if the token doesn’t exist in the mapping or has been marked invalid.
2. Get Access Grant
let grant: AccessGrant = access_grants.get(access_token);
Fails if the token is not found in the access_grants mapping.
3. Doctor Address Match
assert_eq(grant.doctor, doctor);
Fails if the doctor address doesn’t match the one specified in the grant. Prevents token sharing.
4. Record ID Match
assert_eq(grant.record_id, record_id);
Fails if the record ID doesn’t match the one specified in the grant. Prevents using a token for different records.
5. Not Revoked
assert(!grant.is_revoked);
Fails if the patient has manually revoked the access.
6. Not Expired
assert(block.height <= grant.expires_at);
Fails if the current block height exceeds the expiration block height.
Code Example
import { AleoClient } from '@aleo/sdk';
// Initialize Aleo client
const client = new AleoClient();
// Doctor scans patient's QR code
const qrData = await scanQRCode();
const { token, recordId } = parseQRData(qrData);
// Doctor's own address
const doctorAddress = client.address;
try {
// Attempt to verify access
await client.execute(
'salud_health_records_v6.aleo',
'verify_access',
[
token, // Access token from QR code
doctorAddress, // Doctor's address
recordId // Record ID from QR code
]
);
// Transaction succeeded - access is valid!
console.log('Access verified successfully');
// Now the doctor can fetch and decrypt the medical record
const encryptedRecord = await fetchMedicalRecord(recordId);
const decryptedData = await decryptRecord(encryptedRecord, doctorPrivateKey);
displayMedicalRecord(decryptedData);
} catch (error) {
// Transaction failed - access is denied
console.error('Access verification failed:', error);
// Handle different failure cases
if (error.message.includes('is_valid')) {
showError('Invalid or revoked access token');
} else if (error.message.includes('grant.doctor')) {
showError('This token is for a different doctor');
} else if (error.message.includes('grant.record_id')) {
showError('This token is for a different record');
} else if (error.message.includes('is_revoked')) {
showError('Patient has revoked access');
} else if (error.message.includes('expires_at')) {
showError('Access token has expired');
} else {
showError('Access denied: Unknown reason');
}
}
Complete Doctor Workflow
// 1. Scan patient's QR code
async function verifyAndAccessRecord() {
// Scan QR code from patient's device
const qrData = await scanQRCode();
const accessData = JSON.parse(qrData);
const {
token,
recordId,
patient,
recordType,
estimatedExpiry
} = accessData;
// Display access request to doctor for confirmation
await confirmAccessRequest({
patientAddress: patient,
recordType: getRecordTypeName(recordType),
expiresAt: estimatedExpiry
});
try {
// 2. Verify access on-chain
await client.execute(
'salud_health_records_v6.aleo',
'verify_access',
[token, doctorAddress, recordId]
);
console.log('✓ Access verified');
// 3. Fetch the encrypted record
// Note: In Aleo, private records are not publicly readable
// The patient must provide the encrypted record data off-chain
const encryptedRecord = await requestRecordFromPatient({
token,
recordId
});
// 4. Decrypt and display
const medicalData = await decryptMedicalRecord(
encryptedRecord,
doctorPrivateKey
);
// 5. Display in EMR system
displayInEMR(medicalData);
// 6. Log access for audit
await logAccess({
patient,
recordId,
timestamp: new Date(),
doctorId: doctorAddress
});
} catch (error) {
handleAccessDenied(error);
}
}
Checking Access Before Verification
You can use get_access_info to check access details without verification:
// Check access grant details (doesn't verify all conditions)
try {
await client.execute(
'salud_health_records_v6.aleo',
'get_access_info',
[token]
);
// Token exists, but may still be expired/revoked
console.log('Token exists in the system');
// Now verify full access
await verifyAccess(token, doctorAddress, recordId);
} catch (error) {
console.log('Token not found');
}
Error Cases
Invalid Token: Token doesn’t exist or was never createdError: Assertion failed: is_valid
Wrong Doctor: The doctor address doesn’t match the grantError: Assertion failed: assert_eq(grant.doctor, doctor)
This prevents doctors from sharing access tokens.
Wrong Record: The record ID doesn’t match the grantError: Assertion failed: assert_eq(grant.record_id, record_id)
This prevents using a token to access a different record.
Revoked Access: Patient has manually revoked the accessError: Assertion failed: !grant.is_revoked
The patient called revoke_access before the natural expiration.
Expired Access: Current block height exceeds expirationError: Assertion failed: block.height <= grant.expires_at
The time-based access has expired. Patient must grant new access.
Verification Timing
// Check current block height vs expiration
async function checkExpirationStatus(
accessToken: string
): Promise<ExpirationStatus> {
try {
// Get current block height
const currentBlock = await client.getBlockHeight();
// Get access grant info (this would require a custom query)
const grant = await queryAccessGrant(accessToken);
const blocksRemaining = grant.expires_at - currentBlock;
const timeRemaining = blocksRemaining * 15; // seconds
return {
isValid: blocksRemaining > 0 && !grant.is_revoked,
blocksRemaining,
timeRemaining,
expiresAt: grant.expires_at,
isRevoked: grant.is_revoked
};
} catch (error) {
return { isValid: false };
}
}
// Example usage
const status = await checkExpirationStatus(token);
if (status.timeRemaining < 3600) { // Less than 1 hour
console.warn('Access expiring soon:', status.timeRemaining, 'seconds');
}
Security Considerations
Three-Factor Verification
The verification requires three matching pieces of information:
- Access Token (what) - Proves permission was granted
- Doctor Address (who) - Proves identity of accessor
- Record ID (which) - Specifies exact record being accessed
All three must match the original grant, preventing:
- Token sharing between doctors
- Using a token for different records
- Unauthorized access attempts
Automatic Expiration
assert(block.height <= grant.expires_at);
Expiration is enforced at the blockchain level:
- Cannot be bypassed by the doctor
- Cannot be extended without patient action
- Automatically enforced by block height comparison
Manual Revocation
assert(!grant.is_revoked);
Patients can revoke access at any time:
- Immediate effect (no delay)
- Cannot be reversed by doctor
- Independent of expiration time
Public Verifiability
The access_grants mapping is public, meaning:
- Access permissions are transparent
- Auditors can verify access grants
- Patients can monitor who has access
- However, medical data remains private in the record
Quick Validity Check
Use access_token_valid mapping for cheap checks:
let is_valid: bool = access_token_valid.get_or_use(access_token, false);
This is faster than reading the full AccessGrant struct.
Caching Strategy
// Cache verification results for short periods
const verificationCache = new Map<string, {
valid: boolean,
expiresAt: number,
cachedAt: number
}>();
async function verifyWithCache(
token: string,
doctor: string,
recordId: string
): Promise<boolean> {
const cacheKey = `${token}-${doctor}-${recordId}`;
const cached = verificationCache.get(cacheKey);
// Cache for 5 minutes max
if (cached && Date.now() - cached.cachedAt < 300000) {
return cached.valid;
}
// Perform actual verification
try {
await client.execute('verify_access', [token, doctor, recordId]);
verificationCache.set(cacheKey, {
valid: true,
expiresAt: Date.now() + 300000,
cachedAt: Date.now()
});
return true;
} catch {
return false;
}
}
Best Practices
- Verify before every access: Don’t assume a previously valid token is still valid
- Handle all error cases: Provide clear user feedback for each failure type
- Log access attempts: Maintain audit trails for compliance
- Check expiration proactively: Warn users before tokens expire
- Never share tokens: Each doctor should scan their own QR code
- Implement retry logic: Handle temporary network issues gracefully
- Cache carefully: Very short cache times to avoid stale data