Photo by Tolga Ulkan on Unsplash

REACT ARCHITECTURE SERIES

React Proje Mimarisi 19 — (Authorization

Daha önceden yetkilendirme modellerinden bahsetmiştim. Bu yazımda React CASL ile yetkilendirme işlemlerini nasıl gerçekleştireceğimiz üzerinde duracağız.

4 min readSep 7, 2023

--

Web uygulamalarında konseptlerden bahsetmiştim. Temelde konseptler aynı ama biz bir takım açık kaynaklı/paralı (3rd party) kütüphane ve servisleri kullanarak veya şirket içerisinde kendimiz geliştirerek bu konseptleri gerçekleştiririz. Yöntemlerimiz veya kullandığımız kütüphaneler farklı olsa bile konseptler ve amaçları değişmez.

Web App Concept Model

Bu konuları isteyenler önceki yazılarımdan daha detaylı olarak okuyabilirler;

Şimdi gelelim CASL ile projemizi yetkilendirme olayına.

Örnek Bir Kullanım Senaryosu

Kullanıcı web uygulamasının bulunduğu URL geldi.

  1. Öncelikle Kimliğini Tanıtması lazım yani Login işlemi tamamlanmalı, bu işlemle birlikte kullanıcıyıya backend API’ye erişebileceği bir Bearer JWT Token gelir.
  2. Bundan sonra kullanıcı profiline erişmeli. Yeni kullanıcının bilgilerini, rolünü vs.. vs.. Elimizde rolü olan bir kullanıcı var. Bu rol Admin, User, Student vb.. olabilir.
  3. Bundan sonra UI kaynaklarına erişim sırasında kullanıcının rolüne göre yetkilendirme yapmamız lazım. Bu UI kaynakları Sayfa, Sayfanın içerisindeki bir bileşen, veya bir düğmeye basma yetkisi olabilir.

İşte bu kısımda devreye Yetkilendirme Modelleri giriyor. Bu konuda daha önceden detaylı bilginiz yok ise aşağıdaki blog yazımı okumanızı öneririm.

Authorization Models (Yetkilendirme Modelleri)

Bu modellerden en esnek olanı Attribute-Based Access Models (Policy-Based) yetkilendirmedir. Farklı UI Frameworkleri bu tip bir yetkilendirme altyapısı oluşturmak için CASL faydalanabilirsiniz.

Peki bu işlemi teknik olarak nasıl gerçekleştireceğiz. Öncelikle yeteneklerimizi ve izinlerimizi tanımlamamız lazım.

import { AUTHORIZATION_ACTIONS as ACTIONS } from "./authorization-actions";
import { AUTHORIZATION_RESOURCES as RESOURCES } from "./authorization-resources";
import { AUTHORIZATION_ROLES as ROLES } from "./authorization-roles";

Temelde 3 tane modelimiz var.

Actions: Hangi eylemleri yapabiliriz. Ben burada sadece bir sayfaya erişim düşündüm siz bunu istediğiniz gibi çeşitlendirebilirsiniz.

export const AUTHORIZATION_ACTIONS = {
ACCESS_PAGE: "access_page",
};

Resources: Bu kısımda sadece Login olduktan sonraki içerideki sayfaları bir kaynak olarak düşündüm siz bunu istediğiniz seviye ve bileşen olarak çeşitlendirebilirsiniz.

export const AUTHORIZATION_RESOURCES = {
DASHBOARD: "dashboard",
PAYMENT: "payment",
};

Role: Sisteminizde hangi rollerde kullanıcılar olacak.

export const AUTHORIZATION_ROLES = {
ADMIN: "ADMIN",
USER: "USER",
};

Bizim senaryomuzda bir yetki yapısı oluşturalım, Admin tüm sayfalara erişebilsin, User ise Payment sayfasına erişemesin.

import { Ability, AbilityBuilder } from "@casl/ability";

Bu durumda casl Ability ve AbilityBuilder modüllerinden faydalanacağız. Aşağıdaki gibi rollere göre kuralları belirlediğimiz bir yapı üzerinden bir servis sunacağız.

export const AppAbility = Ability;

export default function defineRulesFor(role) {
const { can, cannot, rules } = new AbilityBuilder(AppAbility);

switch (role) {
case ROLES.ADMIN:
can(ACTIONS.ACCESS_PAGE, RESOURCES.DASHBOARD);
can(ACTIONS.ACCESS_PAGE, RESOURCES.PAYMENT);
break;
case ROLES.USER:
can(ACTIONS.ACCESS_PAGE, RESOURCES.DASHBOARD);
cannot(ACTIONS.ACCESS_PAGE, RESOURCES.PAYMENT);
break;
default:
break;
}

return rules;
}

export function buildAbilityFor(role) {
return new AppAbility(defineRulesFor(role), {
detectSubjectType: (object) => object.type,
});
}

Amacımız kullanıcı sisteme login olunca elimizdeki bir rol ile ability yapısını oluşturmak. Web uygulamamıza ilk sayfasına geldiğimizde o sisteme başka bir tab’ da login olmuş olabiliriz veya ilk defa o sisteme giriyor olabiliriz.

const ability = buildAbilityFor(user.role);

const queryClient = new QueryClient();
const root = ReactDOM.createRoot(document.getElementById("root"));
root.render(
<React.StrictMode>
<BrowserRouter>
<AbilityContext.Provider value={ability}>
<QueryClientProvider client={queryClient}>
<>
<App />
</>
</QueryClientProvider>
</AbilityContext.Provider>
</BrowserRouter>
</React.StrictMode>
);

Bizim örneğimizde ilk defa sisteme gelmiş bir kullanıcı olsun. Login olmadığı için rolü yok ve hiç bir yetki alamayacak. Burada ama bir Ability Context oluşturulup bu Context Provider Pattern ile App tüm bileşenlerine geçirilir.

Not: Provider Pattern ile ilgili detaylı bilgi almak için aşağıdaki blog yazısını okuyun.

React Kütüphanelerinde Provider Pattern Kullanımı

Şimdi gelelim Login sonrasında elimizde JWT ile JSON Web Token ile user role çekmiş olalım.. Bu durumda Context içerisinde bulunan yeni Ability kurallarına göre güncellememiz gerekecek.

import { AbilityContext } from "authorization/can";
import defineRulesFor from "authorization/permissions";
//TODO fetch User Profile

const ability = useContext(AbilityContext);
ability.update(defineRulesFor(user.role));

Şimdi artık Ability uygulamamızın farklı kısımlarında kulllanabiliriz. Örneğin Routing için zaten Private/Public sayfalar olan için Routing biraz daha genişleterek kullanıcının yetkisi var mı ? Yoksa not_authorized sayfasına yönlendir diyebiliriz.

export function RouteWithRedirections({ ...props }) {
const ability = useContext(AbilityContext);
const isAuthanticated = isUserLoggedIn();

//Authorization Over RouteId...
const routeId = getRouteId(props?.routeData?.path);
const isAccess = ability.can(AUTHORIZATION_ACTIONS.ACCESS_PAGE, routeId);

//Below Authantication Flow Executed...
if (isAuthanticated) {
if (isOutSidePage(props?.routeData?.path)) {
return <Navigate to={getRoutePath(ROUTES_ID.dashboard)} />;
} else {
if (isAccess) {
//Inside Pages also Check isAccess
return <>{props.children}</>;
} else {
return <Navigate to={getRoutePath(ROUTES_ID.not_authorized)} />;
}
}
} else {
if (isOutSidePage(props?.routeData?.path)) {
return <>{props.children}</>; //Not redirect user are outside and anonymous
} else {
return <Navigate to={getRoutePath(ROUTES_ID.login)} />;
}
}
}

Okumaya Devam Et 😃

Bu yazının devamı veya yazı grubundaki(react-mimarisi) diğer yazılara erişmek için bu linke tıklayabilirsiniz.

Bu yazının devamı veya yazı grubundaki(react-routing) diğer yazılara erişmek için bu linke tıklayabilirsiniz.

--

--