React ile Authentication ve Rol Bazlı Yetkilendirme
Bu gönderide bir react projesi üzerinden react router ve typescript kullanarak bir kullanıcının login olma süreçlerini, sayfalara erişim izni kontrolü, serverdan gelen tokeni saklama ve sayfalarda rol bazlı yetkilendirmeleri nasıl yapacağımızı inceleyeceğiz
Projede aşağıdaki bağımlılıkları kullanacağız. Typescript olmadan da adımları izleyebilirsiniz.
- React 18
- React Router v6
- Typescript 4.6
Projenin github linkine aşağıdan ulaşabilirsiniz.
Bu projede jwt’den sıkça bahsedeceğiz onun için şu gönderiyi okuyabilirsiniz: Jwt Nedir?
Giriş
App.tsx/js içinde Route’ları tanımlayalım.
1 2 3 4 5 6 7 8 9 10 11 |
<div className="App"> <Routes> <Route path="/" element={<HomeLayout />}></Route> <Route element={<Adminlayout />}> <Route path="private2/private" element={<Private2 />} /> <Route path="private" element={<PrivatePage />} /> </Route> <Route path="login" element={<Login />}></Route> <Route path="*" element={<p>Not Found</p>}></Route> </Routes> </div> |
HomeLayout : public sayfa herhangi bir auth işlemi gerekmiyor
AdminLayout : authentication gerektiren sayfalar için hem layout hem de genel korumayı burada sağlayacağız
AdminLayout.tsx :
1 2 3 4 5 6 7 8 9 10 11 |
import { Outlet } from "react-router-dom"; const Adminlayout = () => { return ( <div> <h3>Admin layout</h3> <Outlet /> </div> ); }; export default Adminlayout; |
<Outlet /> render olacak componentin yeri.
Bu sayfaları korumak için bir PrivateRoute adında bir component yapacağız. Bu component giriş yapıp yapmadığını kontrol edeceğiz eğer ki giriş yapmış ise ilgili componenti render edeceğiz değilse login sayfasına yönlendireceğiz.
PrivateRoute.tsx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Navigate, useLocation } from "react-router-dom"; import useAuth from "./useAuth"; interface IPrivateProps { children: React.ReactNode; } const PrivateRoute = ({ children }: IPrivateProps) => { const { isAuthenticated } = useAuth(); const location = useLocation(); return ( <> {isAuthenticated ? ( children ) : ( <Navigate to={"/login"} replace state={{ location }} /> )} </> ); }; export default PrivateRoute; |
useAuth, auth işlemlerini yapacağımız bir hook. Bu hook şimdilik kullanıcın authenticate olup olmadığını dönecek. ileride onu biraz daha detaylandıracağız. state = location ile redirect edeceğimiz sayfanın bilgilerini alacağız.
PrivateRoute componentini şimdi routeları tanımladığımız app.tsx de kullanalım
1 2 3 4 |
<Route element={<PrivateRoute><Adminlayout /></PrivateRoute> } > <Route path="private2/private" element={<Private2 />} /> <Route path="private" element={<PrivatePage />} /> </Route> |
/private olarak bir istek yapıldığında ilk olarak AdminLayout render olacak onun için de öncelikle Higher Component olara çevrelediğimiz PrivateRoute render olacak. Bu componentte yukarıda yaptığımız gibi bazı kontroller yapacak ve authenticate olduysa ilgili komponenti render edecek değil ise login’e yönlendirecek.
Buraya kadar yaptıklarımız temel vermek içindi. Şimdi biraz daha detaya girelim
Kullanıcın Login olması ve bilgilerinin tutulma işlemi
Kullanıcı başarılı bir şekilde login olduktan sonra server bize bir token döndürecek bu token tipi JWT’dir. Bizim bu tokeni browser üzerinde saklamamız gerekiyor ki her seferinde Serverden bu tokeni istemeyelim. Bu tokeni saklamak için iki seçeneğimiz var. Cookie ve Local/Session Storage. İkisinin de avantajı ve dezavantajları var

Cookie
Key value şeklinde tutulur. Eğer ki HttpOnly = true şeklinde kaydedilirse javascript ile erişilemez ve değiştirilemez server için tutulur. Eğer client tarafında gerekli ise true olmamalı. Expire süresi de belirlenebilir, belirlenmez ise sekme kapatıldığında ilgili çerezler silinir. httponly = false olan çerezlere document.cookie üzerinden erişilebilir ve değiştirilebilir. Bu değiştirebilme yetkisiyle beraber bazı güvenlik açıkları da ortaya çıkıyor. Bu açıklardan biri CSRF dir.
CSRF bir saldırı tipidir. Kullanıcının post işlemlerinde istenmeyen aksiyonlara sokabilir. örneğin bir formun içine yeni veri ekleyebilir verileri manipüle edebilir.
Local/Seesion Storage
Cookie gibi aynı şekilde key value olarak tutulur. Bunun server çözümü yoktur. LocalStorage apisi üzerinden verileri erişilebilir. Buradaki güvenlik açığı ise XSS dir. buradan bir veriyi post ettiğinizde eğer ki veritabanına vs. kaydediliyorsa bir javascript kodu yazılıp manipüle edilebilir.
Biz daha az açık içerdiği için cookie’de tutacağız bilgileri. Şimdi cookie’den token okuma ve yazma için bir tokenUtil.ts yazacağız
tokenUtil.ts:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
import jwtDecode from "jwt-decode"; const decodeToken = <T>(token: string) => { try { return jwtDecode<T>(token); } catch (e) { return null; } }; const getToken = () => { return document.cookie .split("; ") .find((row) => row.startsWith("token=")) ?.split("=")[1]; }; const setTokenToCookie = (token: string) => { document.cookie = `token=${token}; path=/`; }; export { decodeToken, getToken, setTokenToCookie }; |
jwt-decode, tokeni bir objeye çevirir, eğer valid değilse null döner. npm ile yükleyebilirsiniz.
Bu decode edilmiş tokeni de bir context üzerinde saklayarak diğer Componentlerin erişebilmesini sağlayacağız bunun için AuthContext.tsx adında bir context oluşturalım
AuthContext:
1 2 3 4 5 6 7 8 |
interface IAuthProps { children: ReactNode; } const AuthContext = createContext({ token: "", onLogin: (token: string) => {}, onLogout: () => {}, }); |
Provider:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
const AuthProvider = ({ children }: IAuthProps) => { const navigate = useNavigate(); const location = useLocation(); const [token, setToken] = useState(getToken() || ""); const handleLogout = () => { setToken(""); setTokenToCookie(""); }; const handleLogin = (token: string) => { setToken(token); setTokenToCookie(token); var returnPath = Object.values(location.state as Location); navigate(returnPath[0].pathname || "/admin"); }; const value = { token, onLogout: handleLogout, onLogin: handleLogin, }; return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>; }; |
useLocation ile kullanıcının redirect olmasını istediği sayfanın bilgilerini alıyoruz eğer direkt login sayfasına girmiş ise de /admin ‘e yönlendiriyoruz. Oluşturduğumuz AuthContext”e diğer componentlerin erişebilmesi için App.tsx de üst component olarak ekleyelim.
1 2 3 4 5 |
<AuthProvider> ...Route ...Route ... </AuthProvider> |
Şimdi useAuth hooksundan contextin verilerini okuyup tokeni decode edip kontrol edebiliriz
useAuth.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
interface IAuth { isAuthenticated: boolean; name: string; } const useAuth = () => { var { token } = useContext(AuthContext); var auth = decodeToken<IAuth>(token); var isAuthenticated = false; if (auth) { isAuthenticated = auth.isAuthenticated; } return { isAuthenticated }; }; export default useAuth; |
token nesnemiz decode olduğunda IAuth tipinde bekliyoruz böyle değilse null dönecektir. şimdi de Login sayfamızda bir context aracılığıyla login olalım
Login.tsx:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
const Login = () => { const { isAuthenticated } = useAuth(); const { onLogin } = useContext(AuthContext); const signIn = () => { //api return token setToken( "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik9rYW4gQ2FuIEthcmFkYcSfIiwiaXNBdXRoZW50aWNhdGVkIjp0cnVlfQ.RwRNm0GPuL_aZF0TI1Lw3i1MsGJRjsndC-iGot7hPOo" ); }; const setToken = (token: string) => { onLogin(token); }; return isAuthenticated ? ( <Navigate to="/" />) : ( <div> <h1>Login Page</h1> <button type="button" onClick={() => signIn()}> Sign in </button> </div> ); }; export default Login; |
Bir apimiz olmadığı için jwt.io üzerinden IAuth tipinde bir token oluşturdum. Siz de buradan bir token oluşturabilirsiniz.

Artık /private istek attığımızda bir token doğrulaması yapıp ona göre yönlendirme yapacak.
Role Bazlı Yetkilendirme
Şimdi authenticate işlemlerini gayet güzel bir şekilde yaptık. Ama bazı sayfalar için kullanıcının sadece giriş yapması yetmeyebilir örneğin sadece adminlerin erişebilieceği ya da manager rolündeki kullanıcıların erişebileceği sayfalar olabilir bunlar içinde bir kontrol yapmamız lazım eğer izni olmayan bir sayfaya erişim sağlamaya çalıştıysa da bir yönlendirme yapalım.
Roles adında bir parametre ekleyeceğiz ve bunu private route aracılığıyla props olarak göndereceğiz ve aşağıdaki şekilde bir algoritma olacak

Yukarıda görüldüğü gibi eğer kullanıcı giriş yapmamış ise login page yönlendirme yapıyoruz login olduysa giriş yapmaya çalıştığı sayfanın bir yetkilendirmesi var mı ona bakıyoruz eğer sayfa da ekstra bir izin yoksa sayfayı gösteriyoruz ama eğer bir izin gerekiyorsa kullanıcının yetkilerine bakıyoruz yetkilerinde bu sayfaya erişim var ise sayfayı gösteriyoruz yok ise yetkiniz yok şeklinde bir mesaj gösteriyoruz.
Şimdi yukarıdaki mantığı işletmemiz için bir role mantığı ekleyeceğiz sayfanın erişim izni verdiği roller ve kullanıcıya ait roller. ve iş katmanımızda bunları kontrol edeceğiz.
PrivateRoute.tsx içine bir tane roles adında nullable parametre ekleyelim.
1 2 3 4 5 6 7 8 9 10 11 12 |
interface IPrivateProps { children?: React.ReactNode; roles?: string[]; } const PrivateRoute = ({ children, roles }: IPrivateProps) => { const { isAuthenticated, isAllow } = useAuth({ roles }); const location = useLocation(); if (!isAuthenticated) { return <Navigate to={"/login"} replace state={{ location }} />; } return <>{isAllow ? children : <div>You are not allowed to access this page</div>}</>; }; |
useAuth hooksu ile rolleri kontrol edeceğiz.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
interface IAuth { isAuthenticated: boolean; name: string; roles?: string[]; } interface IAuthProps { roles?: string[]; } const useAuth = (props?: IAuthProps) => { var { token } = useContext(AuthContext); var auth = decodeToken<IAuth>(token); var isAuthenticated = false; var isAllow = true; if (auth) { isAuthenticated = auth.isAuthenticated; if (isAuthenticated && props && props.roles) { isAllow = props.roles.some((role) => auth?.roles?.includes(role)); } } return { isAuthenticated, isAllow }; }; |
şimdi ise uygulama da gösterelim önce jwt.io üzerinden objemize roles ekleyelim

oluşturduğunuz tokeni eklemeyi unutmayın.
İstediğimiz sayfanın roles propuna yazarak kontrol sağlayabiliriz.
1 2 3 4 5 6 7 8 |
<Route path="admin/manager" element={ <PrivateRoute roles={["manager"]}> <Private2 /> </PrivateRoute> } /> |
Sonuç
İlk olarak şunu unutmayınız, browser tarafında ne yaparsak yapalım buradaki bütün koruma aşılabilir. Burada yaptıklarımız sadece kullanıcıyı düzgün yönlendirebilmemiz için, önemli olan API/Servis tarafında güvenlik sağlamaktır. O yüzden önemli işlemlerinizi clienta koymamalıyız.
Genel olarak bu gönderide bir kullanıcının giriş yapma serüvenini inceledik. Neler olabileceğini sayfaların güvenliğini nasıl sağlayacağımızı ve yönlendirmeleri nasıl yapabileceğimizi öğrendik. Projenin linkine aşağıdan ulaşabilirsiniz.
Proje Linki : okankrdg/React-Authorize-Authentication (github.com)
Okuduğunuz için Teşekkürler İyi Çalışmalar 🙂