RBAC: TypeScript ile Type-Safe Rol Bazlı Erişim Kontrolü
TypeScript ile type-safe bir RBAC sistemi oluşturun, birleşik bir can() fonksiyonu yazın, UI ve backend'de izinleri senkronize edin ve RBAC'ın sınırlarını anlayın.
Özet
Post 102 yetkilendirmeyi service layer'da merkezileştirdi; izin kontrollerinin nerede yapılacağı sorusunu çözdü. Ancak service layer içindeki izin kuralları hala sabit kodlanmış if/else zincirleri: if (session.role === 'admin') return true. Yeni bir rol eklemek, her servis dosyasındaki her yardımcı fonksiyonu değiştirmek anlamına geliyor.
Bu yazı o zincirleri RBAC (Rol Bazlı Erişim Kontrolü) ile değiştiriyor. Ferraiolo ve Kuhn tarafından 1992'de biçimlendirilen ve NIST tarafından INCITS 359-2004 olarak standartlaştırılan bir modeldir. Tek bir type-safe can() fonksiyonu tüm sabit kodlanmış kontrollerin yerini alıyor. Hem sunucu hem istemci tarafında çalışarak Post 102'nin bir sınırlama olarak belirttiği izin mantığı tekrarını ortadan kaldırıyor.
RBAC Nedir?
NIST Modeli
RBAC, izinleri doğrudan kullanıcılara değil rollere atar. Kullanıcılar, rollere atanarak izinleri edinir. Bu dolaylı bağlantı temel kavramdır: birisi görev değiştirdiğinde elli ayrı izni değil rolünü değiştirin.
NIST modeli üç temel bileşen tanımlar:
- Kullanıcılar: Kimliği doğrulanmış kimlikler (bizim durumumuzda
verifySession()) - Roller: İsimlendirilmiş görev fonksiyonları: admin, editor, author, viewer
- İzinler: Kaynaklar üzerinde onaylanmış operasyonlar:
document:create,project:read
Roller olmadan, izin yönetimi N kullanıcı çarpı M izin ataması gerektirir. Rollerle çok daha küçük bir küme yönetirsiniz: N kullanıcı-rol ataması artı R rol-izin eşlemesi.
Bizim Domainimizde RBAC
NIST kavramlarını Post 101-102'deki domain'e eşleyelim:
Kullanıcılar rollere bağlanır. Roller izinlere bağlanır. Hiçbir kullanıcı doğrudan bir izne bağlanmaz. Bu, temel RBAC yapısıdır.
TypeScript İzin Tanımları
Kaynaklar ve Eylemler
İlk adım, hangi kaynakların ve eylemlerin var olduğunu tanımlamaktır. TypeScript'in as const ifadesi, string[]'e genişletmek yerine literal türleri korur.
Permission türü, tüm geçerli Resource:Action kombinasyonlarının bir birleşimidir. 'document:fly' yazmak derleme zamanı hatası olur. TypeScript bu birleşimi iki as const dizisinden otomatik olarak oluşturur.
Roller
İzin Haritası
RBAC sisteminin çekirdeği budur: her rolün izinlerini tanımlayan tek bir nesne.
Burada iki TypeScript özelliği birlikte çalışır:
as constliteral türleri korur; her izinstringdeğil'document:create'olarak kalırsatisfiesşekli genişletmeden doğrular. Bir rol yanlış yazılırsa veya bir izin dizgisi geçersizse (örneğin'document:fly'), TypeScript bunu derleme zamanında yakalar. Ancak çıkarsanan tür yine de belirli literal değerleri koruyarak otomatik tamamlama sağlar
Neden tür anotasyonu yerine satisfies? const ROLE_PERMISSIONS: Record<Role, Permission[]> yazmak tüm değerleri Permission[]'e genişletir ve belirli literal bilgiyi kaybeder. satisfies ile TypeScript her rolün tam olarak hangi izinlere sahip olduğunu bilir.
İzin Matrisi
Yukarıdaki ROLE_PERMISSIONS nesnesi bir tablo olarak görselleştirilebilir. Bu tablo gözden geçirilebilir, denetlenebilir ve kod okumayı gerektirmez.
Bu tablo izin sisteminin kendisidir. "Bir editör ne yapabilir?" sorusu tek bakışta cevaplanır.
can() Fonksiyonu
Uygulama
Tek bir fonksiyon, tüm sabit kodlanmış rol kontrollerinin yerini alır.
Temel özellikler:
- Saf fonksiyon: Veritabanı erişimi yok, async yok, yan etki yok. Sıfır ek yük.
- Type-safe: TypeScript
role'ü dört literal türden birine daraltır. Geçersiz kaynak veya eylem dizgileri derleme zamanı hatalarıdır. - İki form:
can(role, 'document', 'update')servis metotlarında doğal okunur.hasPermission(role, 'document:update')UI bileşenlerinde doğal okunur. İkisi de aynıROLE_PERMISSIONSharitasını sorgular.
Öncesi ve Sonrası: Service Layer Yeniden Düzenleme
Öncesi (Post 102'den sabit kodlanmış kontroller):
Sonrası (can() ile RBAC):
Ne değişti:
if (session.role === 'admin') return trueifadesiif (can(session.role, 'document', 'update')) return trueoldu- Admin kontrolü artık özel durum değil.
adminrolünün izin haritasındadocument:updateolduğu için çalışıyor. - Belgeleri düzenleyebilen bir "moderator" rolü eklemek,
ROLE_PERMISSIONS'a bir satır eklemek demek; bu fonksiyonu değiştirmek değil. - Sahiplik kontrolü (
document.authorId === session.userId) hala birif/elseolarak kalıyor. RBAC "sadece kendinize ait olanlar" ifadesini karşılayamaz. Bu, açıkça RBAC'ın sınırlaması ve Post 104'teki ABAC'ın motivasyonudur.
Ayrıntılı İzinler
CRUD Ötesi: Eylemleri Genişletme
Bir uygulama büyüdükçe basit CRUD tüm operasyonları kapsamaz. Diziye yeni eylemler eklenebilir:
Permission template literal türü otomatik olarak genişler. document:archive, project:invite-member ve project:manage-settings ek tür tanımı olmadan geçerli izin dizgileri haline gelir.
Departman Bazlı İzin Yardımcısı
Kuruluşlarda departmanlar olduğunda, yaygın bir kalıp hem rol iznini HEM DE departman üyeliğini kontrol etmektir:
Departman kontrolünün can() fonksiyonunun dışında olduğuna dikkat edin. RBAC'ın can() fonksiyonu yalnızca roller ve izinler hakkında bilgi sahibidir. "Hangi departman" veya "hangi belirli kaynak" kavramı yoktur. Bu, bir veya iki bağlamsal kontrol için işe yarar. Ancak bunlar çoğaldıkça (departman, sahiplik, zaman, belge durumu) yardımcı fonksiyonlar artar. Aynı soruna geri döneriz.
Sahiplik Bazlı İzin Yardımcısı
Yine, sahiplik kontrolü RBAC'ın dışındadır. can() fonksiyonu "sadece kendi belgeleriniz" ifadesini karşılayamaz. Bu tasarım gereğidir. RBAC rolleri izinlere eşler, o kadar.
UI ve Backend Senkronizasyonu
Paylaşılan İzin Modülü
Temel kavram: lib/permissions.ts dosyasında 'server-only' import'u yoktur. Veritabanı erişimi, oturum yönetimi veya sunucuya özgü API içermez. Tür tanımları, sabit bir nesne ve saf fonksiyonlardan oluşan bir TypeScript modülüdür.
Bu, şunlar tarafından import edilebileceği anlamına gelir:
- Service layer dosyaları (server-only): yetkilendirme için
- React Server Component'ları: koşullu render için
- React Client Component'ları: koşullu render için
- Middleware: route düzeyinde kontroller için
Tek doğruluk kaynağı. Sıfır tekrar.
Server Component Kullanımı
Server Component'lar verifySession() çağırabilir ve can() fonksiyonunu doğrudan kullanabilir:
Post 102'nin versiyonuyla karşılaştırın:
Bileşendeki can() çağrısı ve service layer'daki can() çağrısı aynı ROLE_PERMISSIONS nesnesini sorgular. Bir editör silme izni kazanırsa tek bir yerde değiştirin. Hem sunucu hem istemci değişikliği otomatik olarak yansıtır.
Geçirilen İzinlerle Client Component
Client Component'lar verifySession() çağıramaz. Çözüm: izinleri bir üst Server Component'ta çözümleyin ve prop olarak geçirin.
Client Component izin sistemi hakkında hiçbir şey bilmez. Boolean'lar alır ve buna göre render eder.
PermissionGate Bileşeni
Şablonlarda tekrarlanan izin kontrolleri için yeniden kullanılabilir bir sarmalayıcı standart kodu azaltır:
Kullanım:
Warning: UI izin kontrolleri yalnızca kullanıcı deneyimi içindir; güvenlik için değil. Gizlenmiş bir buton yine de doğrudan API isteğiyle çağrılabilir. Service layer güvenlik sınırı olmaya devam eder. UI kontrolleri kullanıcıların yapamayacakları eylemleri görmesini engeller; service layer bu eylemleri çalıştırmasını engeller.
RBAC Sınırlamaları: can() Yetmediğinde
Bağlamsal Kararlar
can(role, 'document', 'update') genel bir soruyu yanıtlar: "Editörler belgeleri güncelleyebilir mi?" Ancak asıl soru genellikle şudur: "Bu editör bu belirli belgeyi güncelleyebilir mi?" Bu şunlara bağlıdır:
- Editör belgenin projesinin üyesi mi?
- Belge düzenleme için kilitli mi?
- Belge "inceleme" durumunda mı?
- Kullanıcı belgenin yazarı mı?
Bunların hiçbiri rol-izin haritasında ifade edilemez. Kullanıcının, kaynağın ve ortamın niteliklerini gerektirirler.
İzin Matrisi Patlaması
Gereksinimler büyüdükçe, ekipler giderek daha spesifik izinler oluşturur:
document:update: geneldocument:update-own: sadece kendinize aitdocument:update-in-department: sadece departmanınızdakidocument:update-draft: sadece taslaklardocument:update-published: sadece yayınlanmış belgeler
İzin matrisi patlar. Bu, NIST'in "rol patlaması" sorununun kod düzeyindeki karşılığıdır. Her uç durum için yeni roller oluşturmak yerine yeni izin dizgileri oluşturuyoruz; aynı sorunun farklı bir görünümü.
Yardımcı Fonksiyon Çoğalması
RBAC'ın yanında bağlamsal kontrolleri yönetmek için yardımcı fonksiyonlar çoğalır:
Her biri özel bir if/else zinciridir. can() fonksiyonu rol kontrolünü yapar, ancak bağlamsal mantık hala zorunlu koddur. Post 102'den ilerleme kaydettik, ancak bağlamsal kontroller dağınık kalmaya devam ediyor.
RBAC Ne Zaman Kullanılır, Ne Zaman Ötesine Geçilir
RBAC, izinler birincil olarak kullanıcının rolüne bağlı olduğunda doğru araçtır: "Admin'ler her şeyi yapabilir", "Editörler güncelleyip yayınlayabilir", "Viewer'lar sadece okuyabilir." RBAC, izinler bağlama bağlı olduğunda yanlış araçtır: sahiplik, kaynak durumu, takım üyeliği veya çevresel koşullar.
Sırada Ne Var
can() fonksiyonu ve ROLE_PERMISSIONS haritası rol bazlı yetkilendirmeyi temiz bir şekilde çözer. Yeni bir rol eklemek tek satırlık bir değişikliktir. İzin matrisi incelenebilir ve denetlenebilir. Sunucu ve istemci tek bir doğruluk kaynağını paylaşır.
Ancak her bağlamsal kontrol (sahiplik, departman kapsamı, belge durumu) hala RBAC'ın dışında özel bir yardımcı fonksiyon olarak yaşar. Bunlar çoğaldıkça, sistem RBAC'ın sağladığı bildirimsel netliği kaybeder.
Post 104'te Nitelik Bazlı Erişim Kontrolü'nü (ABAC) tanıtıyoruz. can() fonksiyonu, kullanıcının, kaynağın ve ortamın niteliklerini bir politika motoru aracılığıyla değerlendirmeye evrilir. Sahiplik, departman kapsamı ve kaynak durumu politika kurallarına dönüşür; sabit kodlanmış koşullara değil.
Service layer kalıyor. Mimari değişmiyor. Sadece izin kontrollerinin içindeki karar motoru gelişiyor.
Kaynaklar
- NIST Role-Based Access Control (RBAC) Project - RBAC için NIST'in temel proje sayfası, resmi tanım ve INCITS 359 standart referansları
- The NIST Model for Role-Based Access Control (Sandhu, Ferraiolo, Kuhn, 2000) - Dört RBAC model seviyesini tanımlayan temel makale: düz, hiyerarşik, kısıtlı ve simetrik RBAC
- OWASP A01:2025 - Broken Access Control - Bozuk erişim kontrolü OWASP Top 10'da birinci sırada kalmaya devam ediyor; yapılandırılmış izin sistemlerinin neden kritik olduğunu pekiştiriyor
- OWASP Authorization Cheat Sheet - Merkezi yetkilendirme mantığı, varsayılan olarak reddetme ve en az ayrıcalık ilkesi için en iyi uygulamalar
- TypeScript 4.9: The satisfies Operator -
ROLE_PERMISSIONSkalıbında kullanılansatisfiesoperatörü için resmi dokümantasyon - TypeScript Template Literal Types -
Permissiontürünü oluşturan template literal türleri için resmi dokümantasyon - RBAC vs ABAC vs ReBAC (Web Dev Simplified) - TypeScript'te kod örnekleriyle üç izin modelinin pratik karşılaştırması
- Role Explosion: The Hidden Cost of RBAC (Permify) - Rol patlaması nedenleri, bakım etkisi ve ince taneli erişim kontrolü ile çözümlerinin analizi
- CASL - Isomorphic Authorization JavaScript Library -
can()fonksiyon kalıbına ilham veren kütüphane; izomorfik, TypeScript-native yetkilendirme - Next.js Authentication Guide (Official) - Data Access Layer kalıbı ve server-only yetkilendirmeyi kapsayan resmi Next.js dokümantasyonu
- Role-Based Access Control (Wikipedia) - RBAC tarihçesi, üç NIST kuralı ve RBAC0-RBAC3 model hiyerarşisinin kapsamlı genel bakışı
Ölçeklenebilir İzin Sistemleri
TypeScript ve Next.js ile ölçeklenebilir izin sistemleri oluşturma rehberi. Basit kontrol mekanizmalarından RBAC ve ABAC'a, oradan multi-tenant yetkilendirme sistemlerine kadar kapsamlı bir seri.