ViraStack
  • Academy
  • About
  • Support
HomeSourcesContribute
ViraStack

A DX-first toolkit for frontend developers: starters, agent rules, form hooks, and a guide.

Products

  • ViraStack Start
  • ViraStack AI
  • ViraStack Mask
  • ViraStack Password
  • ViraStack Guide

Explore

  • Academy
  • About
  • Brand
  • Support

© 2026 ViraStack.

Open source · MIT

Auth ve Güvenlik

05 / 09

Yetkilendirme ve roller

Rol ve izin kontrolü; sayfa, API ve Server Action koruması.

Kimlik doğrulama "sen kimsin?" sorusunu yanıtlar. Yetkilendirme (authorization) ise "bunu yapmaya iznin var mı?" sorusunu. Giriş yapmış her kullanıcı her şeyi yapamaz: müşteri yalnızca kendi siparişini görür, editör ürün günceller, yönetici kullanıcıları yönetir.

Bu konu küçük görünür ama OWASP Top 10:2025'in birinci sırasında Broken Access Control var. OWASP'ın test ettiği uygulamaların %100'ünde bir tür erişim kontrolü hatası bulundu.1

Tip

Bu dersteki kodlar Next.js'te auth dersindeki proje üzerine kuruldu ve next@16.4.2, better-auth@1.7.10 ile Chromium'da test edildi.

401, 403 ve 404

DurumAnlamıNe zaman
401 UnauthorizedKim olduğun belli değilOturum yok ya da geçersiz
403 ForbiddenKim olduğunu biliyorum ama izin yokTekrar giriş yapmak sonucu değiştirmez
404 Not FoundBöyle bir kayıt yokKaydın varlığını bile belli etmek istemiyorsanız 403 yerine

MDN, kaynağın var olduğunu yetkisiz kullanıcıya göstermek istemiyorsanız 403 yerine 404 dönebileceğinizi yazar.2

Rol ve İzin

En yaygın model RBAC'tir (role-based access control): her kullanıcının bir rolü, her rolün bir izin listesi vardır.3 Kodda rolü değil izni kontrol etmek, yeni bir rol eklendiğinde her if satırını değiştirmekten kurtarır:

// lib/permissions.ts
export type Role = "customer" | "editor" | "admin";

const permissions = {
  customer: ["order:read-own"],
  editor: ["order:read-own", "product:update"],
  admin: ["order:read-own", "order:read-all", "product:update", "user:manage"],
} as const satisfies Record<Role, readonly string[]>;

export type Permission = (typeof permissions)[Role][number];

export function hasPermission(role: Role, permission: Permission) {
  return (permissions[role] as readonly string[]).includes(permission);
}

Permission tipi listeden türetildiği için hasPermission(user.role, "order:delete-all") gibi olmayan bir izin TypeScript hatası verir.

RBAC tek başına "bu sipariş bu kullanıcının mı?" sorusunu yanıtlayamaz. OWASP bu yüzden kaydın sahibi, takım ya da saat gibi niteliklere bakan ABAC (attribute-based) ve ilişkilere bakan ReBAC (relationship-based) modellerini daha esnek bulur.3 Pratikte çoğu uygulama ikisini birlikte kullanır: rol genel izni, sahiplik kontrolü de tek bir kaydı belirler.

Rolü Kim Belirler

Rol, kullanıcının değiştirebileceği bir yerde durmamalı. Better Auth'ta role kullanıcı tablosuna input: false ile eklenir; böylece kayıt ya da güncelleme isteğiyle gönderilemez:4

// lib/auth.ts
export const auth = betterAuth({
  // ...
  user: {
    additionalFields: {
      role: {
        type: "string",
        defaultValue: "customer",
        input: false,
      },
    },
  },
});

Testte kayıt isteğine "role": "admin" eklendi. İstek başarılı oldu ama veritabanına customer yazıldı.

Don't

  • Rolü localStorage'da, URL'de (?isAdmin=true) ya da istemcinin gönderdiği bir alanda tutup ona güvenmeyin.5

  • Yalnızca butonu gizleyip işlemi korumasız bırakmayın.

Kontrolü Tek Yerde Toplamak

Önceki dersteki Data Access Layer'a bir yardımcı daha eklenir. Rolü her istekte oturumdan, yani sunucudan okur:

// lib/dal.ts
import "server-only";

export async function getCurrentUser() {
  const session = await auth.api.getSession({ headers: await headers() });

  if (!session) {
    redirect("/sign-in");
  }

  return {
    id: session.user.id,
    email: session.user.email,
    role: session.user.role as Role,
  };
}

export async function requirePermission(permission: Permission) {
  const user = await getCurrentUser();

  if (!hasPermission(user.role, permission)) {
    throw new Error("Forbidden");
  }

  return user;
}

Bir sayfa ya da bileşen artık await requirePermission("user:manage") ile başlar. OWASP'ın iki temel kuralı bu yapıda kendiliğinden uygulanır: varsayılan olarak reddet ve her istekte kontrol et.3

Server Action'ı Korumak

Next.js dokümantasyonu bunu açıkça yazar: Server Action'lar arayüzünüzden bağımsız olarak doğrudan bir POST isteğiyle çağrılabilir. Sayfadaki giriş kontrolü, sayfanın içindeki action'ı korumaz; her action kendi kontrolünü yapmalıdır.5

En sık yapılan hata, giriş kontrolünü yapıp sahiplik kontrolünü unutmaktır. Kullanıcı başka birinin sipariş numarasını gönderir ve işlem gerçekleşir. Buna IDOR (Insecure Direct Object Reference) denir.6

// lib/orders.ts
import "server-only";

export async function cancelOrder(orderId: string) {
  const user = await getCurrentUser();
  const order = await db.order.findUnique({ where: { id: orderId } });

  const canCancel =
    order && (order.userId === user.id || hasPermission(user.role, "order:read-all"));

  if (!canCancel) {
    throw new Error("Not found");
  }

  await db.order.update({ where: { id: orderId }, data: { status: "cancelled" } });
}
// app/orders/actions.ts
"use server";

import { cancelOrder } from "@/lib/orders";

export async function cancelOrderAction(orderId: string) {
  await cancelOrder(orderId);
}

Action ince kalır; giriş, sahiplik ve rol kontrolü Data Access Layer'dadır.5 Test sonuçları:

KimİşlemSonuç
Burak (customer)Ayşe'nin siparişini iptalHata; sipariş open kaldı
Ayşe (customer)Kendi siparişini iptalBaşarılı
Burak (admin yapıldıktan sonra)Ayşe'nin siparişini iptalBaşarılı

Tip

Rastgele ve tahmin edilemez kimlikler (UUID) IDOR'u zorlaştırır ama çözmez. OWASP her nesne için ayrı yetki kontrolü ister.6

Route Handler'ı Korumak

Route Handler da açık bir API uç noktasıdır. Önce oturum, sonra izin kontrol edilir:7

// app/api/orders/[id]/route.ts
export async function GET(_request: Request, { params }: { params: Promise<{ id: string }> }) {
  const session = await auth.api.getSession({ headers: await headers() });

  if (!session) {
    return new Response(null, { status: 401 });
  }

  if (!hasPermission(session.user.role as Role, "order:read-all")) {
    return new Response(null, { status: 403 });
  }

  const { id } = await params;
  // ...
}

Testte giriş yapmamış istek 401, müşteri 403, yönetici 200 aldı.

Arayüzde Göstermek

Kullanıcının yapamayacağı bir butonu göstermemek iyi bir deneyimdir, ama güvenlik değildir. OWASP istemci tarafındaki erişim kontrolüne asla güvenilmemesini söyler.3 React Summit 2025'teki "Security meets Usability" konuşması da aynı ayrımı yapar: frontend'deki izin kontrolü kullanıcı deneyimi içindir, asıl kontrol backend'de olmalıdır.8

async function OrderActions({ orderId }: { orderId: string }) {
  const user = await getCurrentUser();

  if (!hasPermission(user.role, "order:read-all")) {
    return null;
  }

  return <CancelOrderButton orderId={orderId} />;
}

Bu bileşen butonu gizler. Asıl koruma cancelOrder içindeki kontroldür.

Kendin Dene

  • Bir Server Action'daki sahiplik kontrolünü silin, ikinci bir kullanıcıyla giriş yapıp ilk kullanıcının kayıt kimliğini gönderin ve işlemin gerçekleştiğini görün.

  • DevTools > Network sekmesinde bir Server Action isteğini bulun, sağ tıklayıp Copy as fetch ile kopyalayın ve konsolda farklı bir kimlikle tekrar gönderin.

  • permissions nesnesine yeni bir rol ekleyin ve hangi dosyaları değiştirmeniz gerektiğini sayın.

Detaylı Öğrenmek İçin

  • Resmi doküman: OWASP Authorization Cheat Sheet; varsayılan olarak reddetme, en az yetki ve RBAC, ABAC, ReBAC karşılaştırması.3

  • Resmi doküman: Next.js Data Security rehberi; Server Action'ların doğrudan çağrılabilmesi, IDOR örneği ve denetim listesi.5

  • Konuşma: Samhitha Rama Prasad, "Security meets Usability: Crafting Dynamic and Granular Access Control Solutions" (React Summit 2025, 21 dk).8

  • Resmi doküman: OWASP A01:2025 Broken Access Control.1

Kaynaklar

  • 1DocsOWASP Top 10:2025 — A01 Broken Access Control

    Birinci sıra, test edilen uygulamaların %100'ü, IDOR, yetki yükseltme ve önlemler.

  • 2DocsMDN: 403 Forbidden

    401 ile farkı ve 403 yerine 404 dönme seçeneği.

  • 3DocsOWASP: Authorization Cheat Sheet

    En az yetki, varsayılan olarak reddetme, her istekte kontrol, RBAC ve ABAC/ReBAC, istemci tarafı kontrole güvenmeme.

  • 4DocsBetter Auth: Database — Extending core schema

    `additionalFields`, `defaultValue` ve rolü kullanıcının belirleyememesi için `input: false`.

  • 5DocsNext.js: How to think about data security

    Server Action'ların doğrudan POST ile çağrılabilmesi, her action'da yeniden kontrol, IDOR örneği, `searchParams`'a güvenmeme ve DAL ile mutasyon.

  • 6DocsOWASP: Insecure Direct Object Reference Prevention Cheat Sheet

    IDOR tanımı, örneği ve rastgele kimliklerin tek başına yetmemesi.

  • 7DocsNext.js: How to implement authentication — Route Handlers

    Route Handler'da oturum ve rol kontrolü, 401 ve 403.

  • 8VideoSamhitha Rama Prasad: Security meets Usability

    React Summit 2025; RBAC ve ABAC, React'te izin sistemi ve backend'de zorunlu kontrol.

Edit this page

Last updated: October 18, 2026

Previous step04. Next.js'te auth
Next step06. XSS ve CSP