Erişilebilirlik (a11y) ve güvenlik
İlan açıkça "performans, güvenlik ve accessibility standartlarını belirleyip uygulayabilmek" diyor — bu sitedeki tek eksik konuydu, şimdi kapatıyoruz.
Erişilebilirlik — temel ilkeler
| İlke | Pratikte ne demek |
|---|---|
| Semantik HTML önce | <button> yerine <div onClick> kullanma — buton zaten klavye odaklanabilir, Enter/Space ile tetiklenir, ekran okuyucu "buton" der. Bunları sıfırdan ARIA ile taklit etmek gereksiz iştir. |
| Klavye ile her şey yapılabilmeli | Mouse olmadan Tab/Enter/Esc ile tüm akış (form doldurma, modal kapama) tamamlanabilmeli |
| Renk kontrastı | Metin/arka plan kontrast oranı en az 4.5:1 (WCAG AA) — Lighthouse bunu otomatik kontrol eder |
| ARIA — son çare | Semantik HTML yetmediğinde kullan (role, aria-label, aria-describedby). Gereksiz ARIA, hiç ARIA olmamasından daha kötü olabilir. |
Erişilebilir bir form alanı
function FaturaNoAlani({ value, onChange, error }: Props) {
return (
<div>
<label htmlFor="fatura-no">Fatura numarası</label>
<input
id="fatura-no"
value={value}
onChange={(e) => onChange(e.target.value)}
aria-invalid={!!error}
aria-describedby={error ? "fatura-no-error" : undefined}
/>
{error && (
<p id="fatura-no-error" role="alert">{error}</p>
)}
</div>
);
}
// label + htmlFor: ekran okuyucu "Fatura numarası" der, sadece placeholder değil
// aria-describedby: hata mesajını input'a bağlar, ekran okuyucu hatayı da okurErişilebilir bir modal iskeleti
function Modal({ onClose, children }: Props) {
const ref = useRef<HTMLDivElement>(null);
useEffect(() => {
ref.current?.focus(); // açılınca odak modala geçsin
function onKey(e: KeyboardEvent) {
if (e.key === "Escape") onClose(); // Esc ile kapanabilsin
}
document.addEventListener("keydown", onKey);
return () => document.removeEventListener("keydown", onKey);
}, [onClose]);
return (
<div role="dialog" aria-modal="true" ref={ref} tabIndex={-1}>
{children}
</div>
);
}
// Eksik olan (gerçek üretimde eklenmesi gereken): focus trap —
// Tab tuşu modal dışına çıkmamalı, kapanınca odak açan butona dönmeli.alt, düşük kontrast, label'sız input vb.). axe-core kütüphanesi de CI'a entegre edilip her PR'da otomatik taranabilir.Güvenlik — frontend'in sorumlu olduğu kısım
Backend güvenliği (auth, yetkilendirme, SQL injection) ayrı bir konu — burada frontend tarafında gerçekten senin kontrolünde olan 3 şey var.
1. XSS (Cross-Site Scripting)
React varsayılan olarak {deger} ile render edilen her şeyi kaçışlar — XSS'e karşı yerleşik koruma budur. Tehlike, bunu dangerouslySetInnerHTML ile bilerek devre dışı bıraktığın anda başlar.
// GÜVENLİ — React varsayılan olarak metni kaçışlar (escape)
<p>{kullaniciYorumu}</p>
// TEHLİKELİ — kullanıcı girdisini asla ham HTML olarak basma
<div dangerouslySetInnerHTML={{ __html: kullaniciYorumu }} />
// Gerekiyorsa (ör. CMS'ten gelen zengin metin) önce sanitize et:
import DOMPurify from "dompurify";
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(cmsIcerik) }} />CodeBlock.tsx bileşeni dangerouslySetInnerHTML kullanıyor (kod renklendirme için). Güvenli, çünkü içeriği BEN yazdım, kullanıcıdan gelmiyor. Mülakatta sorulursa: "dangerouslySetInnerHTML kullanmak otomatik olarak güvensiz değildir — güvensiz olan, kullanıcıdan gelen veriyi sanitize etmeden orada kullanmaktır."2. CSRF ve auth token saklama
İlan 2 SharePoint sayfasındaki X-RequestDigest zaten bir CSRF önlemiydi. Frontend'te en sık yapılan hata: JWT/auth token'ı localStorage'a yazmak — XSS ile çalınabilir. Daha güvenli yol: HttpOnly cookie.
Set-Cookie: token=xyz; HttpOnly; Secure; SameSite=Strict
// HttpOnly → JavaScript (document.cookie) token'ı OKUYAMAZ — XSS ile çalınamaz
// Secure → sadece HTTPS üzerinden gönderilir
// SameSite → başka bir siteden yapılan isteklerde cookie otomatik eklenmez (CSRF azaltır)localStorage kullanıyor (checklist ilerlemesi, STAR taslakların) ama kimlik/oturum bilgisi DEĞİL — sadece hassas olmayan, kaybolsa da sorun olmayan UI state'i. Kural: "localStorage'a ne koyduğunu, çalınsa ne olacağını düşünerek koy."3. Bağımlılık (dependency) güvenliği
npm audit düzenli çalıştırılır, bilinen CVE'li paketler güncellenir. Bu proje de @reduxjs/toolkit gibi bağımlılıklar kullanıyor — production'a çıkmadan önce npm audit kontrolü rutin bir adım olmalı.
Egzersizler
1) Bir tasarımcı 'bu buton çok kare duruyor, div ile yuvarlak yapalım, tıklama olayını onClick ile bağlarız' dedi. Nasıl cevap verirsin?
<button>'u border-radius ile istediğin kadar yuvarlaklaştırabilirsin — semantiği kaybetmene gerek yok. <div onClick> kullanırsan klavye ile odaklanamaz, Enter'a basınca tetiklenmez, ekran okuyucu onu "buton" olarak duyurmaz — bunları elle (tabIndex, role="button", onKeyDown) yeniden inşa etmek, zaten var olan bir özelliği taklit etmek olur.2) 'localStorage'a JWT token koymak neden riskli, alternatif ne?
HttpOnly cookie'de token JavaScript'e hiç görünmez, tarayıcı onu otomatik gönderir; XSS olsa bile token çalınamaz (CSRF riski için de SameSite ile önlem alınır).