Complete end-to-end flow for eIDAS-compliant prescription signing
Companion page to: AES Architecture Plan
┌─────────────────────────────────────────────────────────────────────────┐
│ CLINICIAN'S USER JOURNEY │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌──────────┐ ┌──────────────┐ ┌─────────────────┐ │
│ │ 1. LOGIN │───▶│ 2. SELECT │───▶│ 3. VIEW PATIENT │ │
│ │ (MFA via │ │ PATIENT │ │ RECORD │ │
│ │ Keycloak) │ │ │ │ │ │
│ └──────────┘ └──────────────┘ └────────┬────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 4. PRESCRIPTION FORM │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Medication Search │ │ Strength / Form / │ │ │
│ │ │ (SNOMED CT / dm+d) │ │ Quantity / Dosage │ │ │
│ │ └─────────────────────┘ └─────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────┐ ┌─────────────────────┐ │ │
│ │ │ Pharmacy Search │ │ Clinical Notes │ │ │
│ │ │ (name / postcode) │ │ (encrypted) │ │ │
│ │ └─────────────────────┘ └─────────────────────┘ │ │
│ │ │ │
│ │ ┌──────────────────────────────────────────────────────────────┐│ │
│ │ │ Issue Immediately (AES sign on save) [TOGGLE] ││ │
│ │ └──────────────────────────────────────────────────────────────┘│ │
│ │ │ │ │
│ │ ▼ (if toggled ON) │ │
│ │ ┌──────────────────────────────────────────────────────────────┐│ │
│ │ │ ☑ I confirm I wish to electronically sign this ││ │
│ │ │ prescription as the prescriber. I understand this is a ││ │
│ │ │ legally binding Advanced Electronic Signature under the ││ │
│ │ │ Human Medicines Regulations 2012. ││ │
│ │ └──────────────────────────────────────────────────────────────┘│ │
│ │ │ REQUIRED │ │
│ └────────────────────────┼─────────────────────────────────────────┘ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 5. SUBMIT PRESCRIPTION │ │
│ │ ┌─────────────────────────────────────────────────────────┐ │ │
│ │ │ [Save Prescription] [Cancel] │ │ │
│ │ └─────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ 6. PRESCRIPTION DETAIL VIEW │ │
│ │ │ │
│ │ ┌─ Prescriber ──────────────────────────────────────────────┐ │ │
│ │ │ Name: Dr. Doctor Clinician │ │ │
│ │ │ Qualifications: Medical Practitioner (GMC) │ │ │
│ │ │ GMC Number: 1234567 │ │ │
│ │ │ Practice: Test Client │ │ │
│ │ │ Contact: doctor@test-client.co.uk │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─ Signature Evidence ───────────────────────────────────────┐ │ │
│ │ │ ✓ Digitally Signed (PAdES-B-LTA) │ │ │
│ │ │ Signed At: 2026-06-19T09:07:57Z │ │ │
│ │ │ MFA Level: 1 │ │ │
│ │ │ Actor ID: 8d860cb4... │ │ │
│ │ │ Signing IP: 203.0.113.42 │ │ │
│ │ │ User-Agent: Mozilla/5.0... │ │ │
│ │ │ Consent: I confirm I wish to... │ │ │
│ │ │ PDF SHA-256: 944196eb... │ │ │
│ │ │ │ │ │
│ │ │ [Download Signed PDF] [Download Evidence Summary] │ │ │
│ │ └────────────────────────────────────────────────────────────┘ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ SYSTEM ARCHITECTURE │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────┐ ┌───────────────────┐ ┌───────────────────┐│
│ │ Clinician's │ │ Keycloak │ │ AES Portal ││
│ │ Browser │ │ (auth.veripath) │ │ (FastAPI) ││
│ │ │ │ │ │ ││
│ │ GP Booking App │ │ ┌─────────────┐ │ │ ┌─────────────┐ ││
│ │ (Django) │ │ │ Realm: │ │ │ │ /api/sign │ ││
│ │ │ │ │ veripath │ │ │ │ /api/pdf │ ││
│ │ ┌─────────────┐ │ │ ├─────────────┤ │ │ │ /api/evidence│ ││
│ │ │ OIDC Client │──┼────┼──┤ Realm: │ │ │ └─────────────┘ ││
│ │ │ (mozilla- │ │ │ │ test-client │ │ │ ││
│ │ │ django-oidc)│ │ │ └─────────────┘ │ │ ┌─────────────┐ ││
│ │ └─────────────┘ │ │ │ │ │ SQLite DB │ ││
│ │ │ │ ┌─────────────┐ │ │ │ (portal │ ││
│ │ ┌─────────────┐ │ │ │ JWKS per │ │ │ │ records) │ ││
│ │ │ AESClient │──┼────┼──┤ realm │ │ │ └─────────────┘ ││
│ │ │ (Python │ │ │ └─────────────┘ │ │ ││
│ │ │ requests) │ │ │ │ │ ┌─────────────┐ ││
│ │ └─────────────┘ │ └───────────────────┘ │ │ Audit Chain │ ││
│ │ │ │ │ (hash-linked│ ││
│ │ ┌─────────────┐ │ │ │ JSONL) │ ││
│ │ │ PostgreSQL │ │ │ └─────────────┘ ││
│ │ │ (booking DB)│ │ └────────┬──────────┘│
│ │ └─────────────┘ │ │ │
│ └───────────────────┘ │ │
│ ┌────────────────────────────────────┼──────────┐│
│ │ Internal Docker Network │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────┐ ││
│ │ │ AES Signer (pyHanko) │ ││
│ │ │ ┌──────────────────────────────┐ │ ││
│ │ │ │ 1. Receive signing request │ │ ││
│ │ │ │ 2. Lookup cert by CN │ │ ││
│ │ │ │ 3. Embed X.509 certificate │ │ ││
│ │ │ │ 4. Request TSA timestamp │ │ ││
│ │ │ │ 5. Produce PAdES-B-LTA PDF │ │ ││
│ │ │ │ 6. Return base64 PDF │ │ ││
│ │ │ └──────────────────────────────┘ │ ││
│ │ └──────────────────────────────────────┘ ││
│ │ │ ││
│ │ ▼ ││
│ │ ┌──────────────────────────────────────┐ ││
│ │ │ AES TSA (Timestamp) │ ││
│ │ │ ┌──────────────────────────────┐ │ ││
│ │ │ │ RFC 3161 timestamp token │ │ ││
│ │ │ │ Signed with TSA cert from │ │ ││
│ │ │ │ PKI │ │ ││
│ │ │ └──────────────────────────────┘ │ ││
│ │ └──────────────────────────────────────┘ ││
│ │ ││
│ │ ┌──────────────────────────────────────┐ ││
│ │ │ AES PKI (Certificate Authority)│ ││
│ │ │ ┌──────────────────────────────┐ │ ││
│ │ │ │ • Self-signed Root CA │ │ ││
│ │ │ │ • Issues clinician certs │ │ ││
│ │ │ │ • Issues TSA certs │ │ ││
│ │ │ │ • CRL management │ │ ││
│ │ │ │ • Keys stored in SoftHSM2 │ │ ││
│ │ │ └──────────────────────────────┘ │ ││
│ │ └──────────────────────────────────────┘ ││
│ └───────────────────────────────────────────────┘│
└─────────────────────────────────────────────────────────────────────────┘
SIGNING TIME DATA CAPTURE
═════════════════════════
┌─────────────────────────────────────────────────────────────────┐
│ WHAT GETS STORED WHERE │
├────────────┬──────────────────────┬──────────────────────────────┤
│ LAYER │ AES PORTAL │ GP BOOKING APP (Postgres)│
│ │ (portal.db) │ (clinical_data_prescrip │
│ │ medication_details │ tion.medication_details) │
├────────────┼──────────────────────┼──────────────────────────────┤
│ Prescription│ prescription_id │ aes_prescription_id │
│ │ clinician_cn │ aes_signed_pdf_path │
│ │ medication_name │ aes_signed_at │
│ │ signed_pdf_path │ aes_status │
│ │ signed_at │ aes_delivery_status │
│ │ status │ aes_pdf_sha256 │
│ │ delivery_status │ │
├────────────┼──────────────────────┼──────────────────────────────┤
│ Actor │ actor_sub │ (stored in AES Portal only) │
│ Evidence │ actor_username │ │
│ │ actor_acr │ │
│ │ actor_auth_time │ │
│ │ actor_ip │ aes_client_ip │
│ │ actor_user_agent │ aes_client_ua │
├────────────┼──────────────────────┼──────────────────────────────┤
│ Consent │ consent_text │ aes_consent_text │
│ │ consent_timestamp │ aes_consent_timestamp │
├────────────┼──────────────────────┼──────────────────────────────┤
│ Medication │ strength │ strength │
│ Details │ form │ form │
│ │ quantity │ quantity │
│ │ dosage │ (dosage_instructions field) │
│ │ snomed_code │ snomed_code │
├────────────┼──────────────────────┼──────────────────────────────┤
│ Pharmacy │ pharmacy_id │ pharmacy_id │
│ │ │ pharmacy_ods │
│ │ │ pharmacy_name │
└────────────┴──────────────────────┴──────────────────────────────┘
DOWNLOAD TIME DATA CAPTURE
══════════════════════════
PDF Download:
→ AES Portal logs to audit_chain.jsonl:
{ event: "prescription_pdf_downloaded",
actor: "doctor",
ip: "203.0.113.42",
user_agent: "Mozilla/5.0...",
prescription_id: "RX-134",
pdf_sha256: "944196eb..." }
Evidence Download:
→ AES Portal logs to audit_chain.jsonl:
{ event: "evidence_summary_downloaded",
actor: "doctor",
ip: "203.0.113.42",
prescription_id: "RX-134" }
Response Headers (PDF):
X-Content-SHA256: 944196eb...
Content-Disposition: attachment; filename="RX-134.pdf"
HASH-CHAINED AUDIT LOG
══════════════════════
┌──────────────────────────────────────────────────────────────────────┐
│ audit_chain.jsonl │
├──────────────────────────────────────────────────────────────────────┤
│ │
│ Entry 1: │
│ { │
│ "prev_hash": "0000000000000000...", ← genesis entry │
│ "hash": "a1b2c3d4...", │
│ "entry": { │
│ "timestamp": "2026-06-19T08:00:00Z", │
│ "event_type": "system_startup", │
│ "actor": "system" │
│ } │
│ } │
│ │
│ Entry 2: │
│ { │
│ "prev_hash": "a1b2c3d4...", ← links to entry 1 │
│ "hash": "e5f6g7h8...", │
│ "entry": { │
│ "timestamp": "2026-06-19T09:07:57Z", │
│ "event_type": "prescription_signed", │
│ "actor": "doctor", │
│ "details": { │
│ "prescription_id": "RX-134", │
│ "actor_sub": "8d860cb4...", │
│ "actor_acr": "1", │
│ "actor_ip": "203.0.113.42" │
│ } │
│ } │
│ } │
│ │
│ Entry 3: │
│ { │
│ "prev_hash": "e5f6g7h8...", ← links to entry 2 │
│ "hash": "i9j0k1l2...", │
│ "entry": { │
│ "timestamp": "2026-06-19T09:10:00Z", │
│ "event_type": "prescription_pdf_downloaded", │
│ "actor": "doctor", │
│ "details": { │
│ "prescription_id": "RX-134", │
│ "ip": "203.0.113.42", │
│ "pdf_sha256": "944196eb..." │
│ } │
│ } │
│ } │
│ │
│ ... each entry hashed with SHA-256 and linked to previous ... │
│ │
└──────────────────────────────────────────────────────────────────────┘
EVIDENCE SUMMARY (downloadable JSON)
═══════════════════════════════════
{
"evidence_type": "PAdES-B-LTA Evidence Summary",
"generated_at": "2026-06-19T10:00:00+00:00",
"generated_by": "doctor",
"prescription": { ← Prescription metadata
"prescription_id": "RX-20260519-0001",
"clinician_name": "Dr Jane Smith",
"patient_name": "John Smith",
"medication_name": "Amoxicillin 500mg capsules",
"status": "signed",
"signed_at": "2026-06-19T10:00:00+00:00"
},
"signed_pdf": { ← Cryptographic linkage
"path": "/data/prescriptions/RX-20260519-0001.pdf",
"exists": true,
"sha256": "944196eb..." ← Tamper evidence
},
"actor_evidence": { ← Identity & sole control
"keycloak_sub": "8d860cb4-...", ← Unique identity
"username": "doctor", ← Human-readable
"authentication_context_ref": "1", ← MFA level (acr)
"auth_time": 1781863823, ← MFA timestamp
"ip_address": "203.0.113.42", ← Forensic evidence
"user_agent": "Mozilla/5.0..." ← Forensic evidence
},
"consent": { ← Intent evidence
"text": "I confirm I wish to electronically sign...",
"timestamp": "2026-06-19T10:00:00Z"
}
}
EVIDENCE PILLARS:
┌─────────────────────────────────────────────────────────────────┐
│ Pillar │ Captured │ Location │
├─────────────────────────────────────────────────────────────────┤
│ Cryptographic │ ✔ │ PAdES-B-LTA PDF, SHA-256 hash │
│ Identity │ ✔ │ Keycloak sub + username + acr │
│ Forensic │ ✔ │ Client IP + User-Agent │
│ Intent │ ✔ │ Consent text + timestamp │
│ Temporal │ ✔ │ TSA token + signed_at │
│ Chain-of-custody │ ✔ │ audit_chain.jsonl hash chain │
│ Tamper evidence │ ✔ │ X-Content-SHA256 header │
└─────────────────────────────────────────────────────────────────┘
KEYCLOAK TOKEN VERIFICATION
══════════════════════════
User JWT Token (decoded):
{
"iss": "https://auth.veripath.co.uk/realms/test-client", ← Realm
"sub": "8d860cb4-c4d7-45c8-a9ff-f0062bd258e2", ← Unique ID
"aud": "account",
"acr": "1", ← MFA level
"auth_time": 1781863823, ← MFA timestamp
"preferred_username": "doctor",
"email": "doctor@test-client.co.uk"
}
AES Portal Verification:
1. Extract "iss" → realm = "test-client"
2. GET /realms/test-client/protocol/openid-connect/certs
3. Match "kid" from JWT header to JWKS key
4. Construct RSA public key from "n" and "e"
5. Verify RS256 signature
6. Check exp, iss claims
7. Extract and store: sub, preferred_username, acr, auth_time
8. Capture: X-Forwarded-For (IP), User-Agent (browser)
┌─────────────────────────────────────────────────────────────────┐
│ TOKEN SOURCE HIERARCHY │
├─────────────────────────────────────────────────────────────────┤
│ │
│ Priority 1: User's OIDC access token │
│ From: request.session['oidc_access_token'] │
│ Evidence: Full (sub, acr, auth_time, IP, UA) │
│ eIDAS: ✓ Sole control (user authenticated with MFA) │
│ │
│ Priority 2: Service account (fallback) │
│ From: Client credentials (aes-signer-sa) │
│ Evidence: Reduced (no acr, no user identity claims) │
│ eIDAS: ⚠ Still functional for signing │
│ │
└─────────────────────────────────────────────────────────────────┘