React + fhirclient; token di browser, PHI, audit
authorize() dan ready(), menampilkan CodeableConcept, lalu penyimpanan token, PHI di log, dan audit
authorize() dan ready() di fhirclient,
menampilkan CodeableConcept dengan urutan text → display → code,
dan menjawab di mana token disimpan di React app serta apa yang dilakukan supaya PHI aman.
Sebelumnya, di lesson 0019: scopes dan Backend Services. Lesson terakhir modul SMART ini kembali ke React app: memakai alur 0017–0018 lewat fhirclient, lalu menjaga token dan PHI.
6. React + fhirclient, dan menampilkan CodeableConcept
Library resmi komunitas SMART untuk JS adalah fhirclient (npm, saat ini v3.x), jalan di browser dan Node 18+.
Di browser biasanya ada dua halaman: launch page (launch_uri) yang memanggil authorize(), dan index page (redirect_uri) yang memanggil ready(). [1]
authorize() berakhir dengan redirect, jadi kode setelahnya mungkin tidak dijalankan. Untuk EHR launch cukup clientId + scope (iss datang dari URL);
untuk standalone tambahkan opsi iss. PKCE dipakai otomatis bila server mendukung (pkceMode: "ifSupported", default). [2]
// launch.tsx: dibuka EHR dengan ?iss=...&launch=...
import { authorize } from "fhirclient/browser";
authorize({ clientId: "my_web_app", scope: "launch openid fhirUser patient/Patient.rs patient/Observation.rs" });
// App.tsx: halaman redirect_uri
import { ready } from "fhirclient/browser";
export function Vitals() {
const [obs, setObs] = useState<Observation[]>([]);
useEffect(() => {
ready() // tukar code + verifier, lalu kembalikan Client
.then(client => client.patient.request("Observation", { flat: true, pageLimit: 0 })) // /Observation?patient=<id>
.then(setObs);
}, []);
return <ul>{obs.map(o => <li key={o.id}>{conceptLabel(o.code)}</li>)}</ul>;
}
client.patient.id berisi ID pasien dari launch context; client.patient.request("Observation") otomatis menjadi /Observation?patient=patient-id;
opsi flat mengembalikan array resource (aman walau Bundle.entry kosong), pageLimit: 0 mengambil semua halaman, dan onPage memproses per halaman supaya hemat memori.
client.request() juga me-refresh token otomatis bila hampir kedaluwarsa dan ada refresh token. [3]
Menampilkan CodeableConcept. text adalah "the representation of the concept as entered or chosen by the user"; satu Coding bisa ditandai userSelected,
dan bila tidak ada yang ditandai, text (bila ada) adalah sumber makna yang diutamakan. display adalah tampilan kode menurut code system-nya. [4]
Jadi UI jangan menampilkan coding[0].code mentah:
function conceptLabel(cc?: CodeableConcept): string {
if (!cc) return "—";
if (cc.text) return cc.text; // 1. apa yang dilihat/dipilih user
const coding = cc.coding ?? [];
const pick = coding.find(c => c.userSelected && c.display) ?? coding.find(c => c.display);
if (pick?.display) return pick.display; // 2. display dari Coding
return coding[0]?.code ?? "(tanpa kode)"; // 3. terakhir: kode mentah
}
Kalau butuh logika per kode (mis. LOINC 8867-4 untuk heart rate), cocokkan system + code, bukan display: display hanya untuk manusia. Lihat lesson 0005 untuk anatomi CodeableConcept/Coding.
Latihan 6 — fhirclient dan CodeableConcept
7. Token di browser, PHI, dan audit (konsep)
- Penyimpanan token. App SHALL NOT menyimpan bearer token di cookie yang dikirim sebagai clear text, dan SHOULD menyimpan token di storage khusus app, bukan lokasi yang bisa ditemukan sistem. [5]
fhirclientdi browser menyimpan state-nya (termasuk token response) disessionStorage. [6] Itu hilang saat tab ditutup, tapi tetap terbaca oleh script hasil XSS, sama sepertilocalStorage(lihat lesson 0017 track .NET). - Public vs confidential. Data di backend memberi binding refresh token yang lebih kuat, tapi kebocoran server bisa mengenai banyak user; app murni di perangkat lebih rentan per device. Pilih berdasarkan konteks. [7] Dalam interview, sebut opsi backend-for-frontend (React → backend Node sebagai confidential client) bila butuh akses panjang.
- Jangan bocorkan token. Jangan ikuti
referenceke server lain sambil membawa access token; abaikan atau minta akses baru. [5] Jangan pernah menjadi "open redirector" dan selalu lewat TLS. [5] - PHI di log. Spesifikasi FHIR R4 memperingatkan risiko seperti "audit logging of the GET parameters into an unprotected audit log": query search FHIR (
?name=…&birthdate=…) sudah berisi data pasien. [8] - Audit. FHIR punya resource
AuditEventuntuk mencatat event keamanan/privasi (siapa mengakses apa, kapan), dipakai untuk reporting dan deteksi penyalahgunaan. [8] [9]
Latihan 7 — Keamanan
Latihan lisan — Jawab lisan
Pasang timer, jawab dengan suara keras seolah sedang interview, baru buka jawaban contoh. Soal ini juga ada di bank soal dan bisa ditarik review 0027.
"Di React app yang menampilkan data pasien, di mana Anda menyimpan token, dan apa yang Anda lakukan supaya PHI aman?" (60 detik)
Bandingkan dengan jawaban contoh dan checklist-nya
"Token saya anggap sama sensitifnya dengan datanya. SMART meminta token disimpan di storage khusus app dan tidak di cookie yang dikirim tanpa enkripsi.
fhirclient menyimpan state-nya di sessionStorage: hilang saat tab ditutup, tapi tetap terbaca kalau ada XSS, jadi CSP dan sanitasi input penting.
Access token sebaiknya maksimal satu jam; kalau butuh akses panjang, saya pindahkan pertukaran token ke backend sebagai confidential client atau pola backend-for-frontend.
Untuk PHI: scope seminimal mungkin, tidak menulis isi resource atau query string ke log biasa atau analytics, tidak mengikuti reference ke server lain dengan token yang sama,
dan akses dicatat di audit log, misalnya resource AuditEvent."
Checklist: apakah Anda menyebut…
- storage khusus app; jangan cookie cleartext; fhirclient memakai
sessionStorage? - XSS tetap bisa membaca storage: CSP, sanitasi?
- token pendek (≤ 1 jam); akses panjang lebih aman lewat backend (confidential/BFF)?
- scope seminimal mungkin (least privilege)?
- jangan log PHI atau query string ke log tak terlindungi?
- audit akses (
AuditEvent), jangan kirim token ke server lain?
Lulus kalau minimal 4 dari 6.
Istilah: GLOSSARY.md (bagian "Keamanan dan SMART on FHIR") · Sumber: RESOURCES.md
Ada yang belum jelas? Tanyakan ke saya (agent) di terminal, misalnya: "apa beda id_token dan access token di SMART?", "kapan app React sebaiknya pindah ke pola backend-for-frontend?", atau "bagaimana server memvalidasi JWT assertion Backend Services (jku, kid)?"
Sumber: [1] fhirclient: overview · [2] fhirclient: SMART API · [3] fhirclient: Client API · [4] FHIR R4 Datatypes: CodeableConcept · [5] App Launch: Launch and Authorization · [6] fhirclient source: BrowserStorage.ts · [7] SMART Best Practices · [8] FHIR R4 Security · [9] FHIR R4 AuditEvent