# Što ovaj vodič pokriva#
Autorizacija je odluka što autentificirani korisnik smije raditi — koje stranice može otvoriti, koje API rute može pozvati te koje retke može čitati ili ažurirati.
Ovaj vodič fokusira se na praktičnu autorizaciju za Next.js App Router aplikacije, koristi ciljanu ključnu frazu Next.js authorization RBAC ABAC i pokazuje kako kombinirati tri sloja:
- Provjere u middlewareu za rana preusmjeravanja i grube “gateove” po rutama
- Zaštite u server komponentama i route handlerima za stvarnu provedbu u vašem Next.js backendu
- Politike provedene u bazi podataka poput PostgreSQL Row Level Security (RLS) za jamstva koja se ne mogu zaobići
Ako još postavljate autentifikaciju, prvo krenite ovdje: Vodič za Next.js autentifikaciju s NextAuth, Clerk i Supabase.
# Mentalni model: RBAC vs ABAC u Next.js-u#
RBAC i ABAC nisu toliko konkurentski okviri koliko su različite dimenzije politike.
- RBAC (Role Based Access Control): odluke temeljene na ulogama poput
admin,member,viewer. - ABAC (Attribute Based Access Control): odluke temeljene na atributima korisnika, resursa i konteksta poput
tenantId,ownerId,plan,region,isSensitive,ipRiskScore.
U stvarnoj App Router aplikaciji tipično ćete koristiti:
- RBAC za navigaciju i pristup na razini sekcija (admin područje, billing područje).
- ABAC za pristup na razini resursa (samo retci vašeg tenanta, samo vaši dokumenti, samo unutar dopuštene regije, samo ako plan to dopušta).
Zašto je ovo posebno važno u App Routeru#
App Router vas gura prema server-side renderingu i Server Components, što je dobro za autorizaciju jer:
- provjere mogu raditi na serveru bez otkrivanja tajni
- možete blokirati podatke na izvoru prije nego dođu do klijenta
- možete smanjiti “flash neautoriziranog sadržaja” tako da odlučite prije renderiranja
Ali to također povećava broj ulaznih točaka koje trebaju dosljednu politiku:
- server komponente koje dohvaćaju podatke
- route handleri u
app/api - server actions
- middleware route matching
- upiti prema bazi
Ako su politike nedosljedne, korisnici (i napadači) će pronaći najslabiju kariku.
# Matrica odluke: kada preferirati RBAC, a kada ABAC#
Koristite ovo kao početnu matricu odluke za Next.js authorization RBAC ABAC.
| Kriterij | RBAC bolje odgovara | ABAC bolje odgovara | Preporuka za Next.js App Router |
|---|---|---|---|
| Veličina tima i brzina | Brže za isporuku i objašnjavanje | Složenije za modelirati | Krenite s RBAC-om, dodajte ABAC gdje treba |
| Multitenancy | Samo za grube tenant uloge | Najbolje za izolaciju tenanta i vlasništvo | ABAC + RLS u bazi za granice tenanta |
| Feature gating po planu | Nezgodno kroz uloge | Prirodno kroz atribute poput plan | ABAC za provjere plana, uloge zadržite za admin pristup |
| Vlasništvo resursa | Nezgodno osim s puno uloga | Prirodno kroz resource.ownerId | ABAC na sloju resursa |
| Audit i usklađenost | Osnovni logovi uloga | Bogat kontekst i uvjeti | Kombinirajte: logirajte ulogu + korištene atribute |
| Rizik “policy drifta” | Niži ako su uloge stabilne | Viši bez discipline | Centralizirajte politike, invarijante provedite u DB-u |
| Non-web klijenti | Treba backend provedbu | Treba backend provedbu | Ne oslanjajte se samo na middleware |
🎯 Ključna poruka: RBAC koristite da odgovorite “smije li ovaj korisnik ući u ovo područje”, a ABAC da odgovorite “smije li ovaj korisnik pristupiti baš ovom zapisu upravo sada”.
# Preporučena slojevita arhitektura#
Sigurna autorizacija u Next.js App Routeru obično izgleda ovako:
- 1Middleware: rano blokira očite slučajeve (neautentificiran, nedostaje tenant, blokirana uloga)
- 2Server-side guardovi: provode odluke u Server Components, route handlerima i server actions
- 3DB politike: provode pravila koja se ne mogu zaobići (izolacija tenanta, vlasništvo) pomoću RLS-a ili ekvivalenta
To odgovara načinu na koji napadači ispituju aplikacije: prvo zaobiđu UI, zatim pokušaju API-je, pa eksploatiraju pristup podacima.
Za širi skup sigurnosnih kontrola izvan autorizacije, koristite naš checklist: Checklist sigurnosti web aplikacija.
# Korak 1: Modelirajte svoje autorizacijske podatke#
Prije koda, definirajte minimalni održivi model politika. Evo dva česta početna pristupa.
Opcija A: RBAC-first shema (jednostavne aplikacije)#
- korisnici imaju jednu ili više uloga
- uloge se mapiraju na dozvole
| Entitet | Primjer polja | Napomene |
|---|---|---|
users | id, email | Identity je u nadležnosti auth providera |
user_roles | user_id, role | Uloga je enum string |
role_permissions | role, permission | Opcionalno ako želite granularne dozvole |
Dobro za: interne dashboarde, single-tenant aplikacije, malo resursa.
Opcija B: Multitenant ABAC-first shema (SaaS)#
- svaki resurs uključuje
tenant_id - članstvo definira korisničke atribute unutar tenanta
- ABAC provjerava atribute poput
tenantRole,plan,ownerId
| Entitet | Primjer polja | Napomene |
|---|---|---|
tenants | id, plan | Plan upravlja feature gateovima |
tenant_memberships | tenant_id, user_id, role | Uloga je po tenantu |
projects | id, tenant_id, owner_id | Ključni ABAC atributi |
Dobro za: SaaS, agencije, bilo koji multi-organizacijski proizvod.
Ako koristite Supabase i trebate obrasce izolacije tenanta, pročitajte: Next.js Supabase Row Level Security za multitenant SaaS.
ℹ️ Napomena: Većina autorizacijskih incidenata u SaaS-u su curenja podataka između tenanata. Ako ste multitenant, izolaciju tenanta tretirajte kao invarijantu baze, ne kao frontend pravilo.
# Korak 2: Autorizacijski gateovi u middlewareu (grubo, brzo, ali ne i konačno)#
Middleware je odličan za:
- preusmjeravanje neautentificiranih korisnika s zaštićenih ruta
- blokiranje ne-admin korisnika od očitih admin sekcija
- osiguravanje da je odabran
tenantprije ulaska u tenant rute
Middleware nije dobar za:
- autorizaciju na razini zapisa (record-level)
- bilo što što ovisi o čitanju iz baze
- kao jedini sloj zaštite
Middleware obrazac: RBAC gate na razini ruta#
Ispod je minimalni obrazac koji očekuje da vaš auth sloj daje claimove poput userId i roles. Točan način dohvaćanja sesije ovisi o auth provideru, pa ovo tretirajte kao predložak.
// middleware.ts
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
const adminOnly = [/^\/admin(\/|$)/];
const protectedRoutes = [/^\/app(\/|$)/, /^\/admin(\/|$)/];
function matches(pathname: string, rules: RegExp[]) {
return rules.some((r) => r.test(pathname));
}
export async function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
if (!matches(pathname, protectedRoutes)) return NextResponse.next();
// PSEUDO: replace with NextAuth/Clerk/Supabase session lookup
const session = await getSessionFromRequest(req);
if (!session?.userId) {
const url = req.nextUrl.clone();
url.pathname = "/login";
url.searchParams.set("next", pathname);
return NextResponse.redirect(url);
}
if (matches(pathname, adminOnly) && !session.roles?.includes("admin")) {
return NextResponse.redirect(new URL("/403", req.url));
}
return NextResponse.next();
}
export const config = {
matcher: ["/app/:path*", "/admin/:path*"],
};ABAC gate u middlewareu: zahtijevaj tenant kontekst#
Čest SaaS obrazac su rute /t/[tenantSlug]/.... Middleware može provjeriti da tenantSlug postoji i da korisnik ima neko članstvo, ali izbjegavajte teške DB pozive.
// middleware.ts (tenant presence check)
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const { pathname } = req.nextUrl;
if (!pathname.startsWith("/t/")) return NextResponse.next();
const parts = pathname.split("/").filter(Boolean);
const tenantSlug = parts[1];
if (!tenantSlug) return NextResponse.redirect(new URL("/select-tenant", req.url));
return NextResponse.next();
}
export const config = {
matcher: ["/t/:path*"],
};⚠️ Upozorenje: Nemojte spremati “autorizacijsku istinu” u kolačiće ili claimove koji postoje samo u middlewareu, osim ako su kriptografski potpisani i kratkog vijeka. Ako ih ne možete pouzdano validirati, tretirajte ih samo kao hintove za UX.
# Korak 3: Guardovi u server komponentama (stvarna provedba za stranice)#
Server Components su mjesto gdje App Router briljira za autorizaciju. Možete dohvatiti sesiju i provesti politike prije renderiranja bilo kakvih osjetljivih podataka.
Obrazac: requireUser i requireRole#
Držite ove funkcije isključivo na serveru i ponovno ih koristite kroz stranice, layoute, server actions i route handlere.
// lib/authz.ts
export type Session = {
userId: string;
roles: string[];
tenantId?: string;
};
export async function requireUser(): Promise<Session> {
const session = await getSessionOnServer(); // provider-specific
if (!session?.userId) {
throw new Error("UNAUTHENTICATED");
}
return session;
}
export function requireRole(session: Session, role: string) {
if (!session.roles?.includes(role)) {
throw new Error("FORBIDDEN");
}
}Zatim to koristite u Server Component stranici:
// app/admin/page.tsx
import { requireUser, requireRole } from "@/lib/authz";
export default async function AdminPage() {
const session = await requireUser();
requireRole(session, "admin");
return <div>Admin dashboard</div>;
}Obrazac: ABAC guard za tenant članstvo#
Za tenant rute često trebate “korisnik je član tenanta X” i možda “uloga u ovom tenantu je barem editor”.
// lib/policies/tenant.ts
export type TenantRole = "owner" | "admin" | "editor" | "viewer";
const rank: Record<TenantRole, number> = {
owner: 4,
admin: 3,
editor: 2,
viewer: 1,
};
export function requireTenantRole(input: {
userRole: TenantRole | null;
minRole: TenantRole;
}) {
if (!input.userRole) throw new Error("FORBIDDEN");
if (rank[input.userRole] < rank[input.minRole]) throw new Error("FORBIDDEN");
}Koristite to unutar tenant stranice nakon dohvaćanja membershipa:
// app/t/[tenantSlug]/settings/page.tsx
import { requireUser } from "@/lib/authz";
import { requireTenantRole } from "@/lib/policies/tenant";
export default async function TenantSettingsPage(props: {
params: Promise<{ tenantSlug: string }>;
}) {
const session = await requireUser();
const { tenantSlug } = await props.params;
const membership = await getMembership({
userId: session.userId,
tenantSlug,
});
requireTenantRole({ userRole: membership?.role ?? null, minRole: "admin" });
return <div>Tenant settings</div>;
}💡 Savjet: Radije bacajte grešku u server guardovima i mapirajte je na 403 stranicu na jednom mjestu, umjesto da posvuda posipate redirecte. Tako policy kod ostaje čist i testabilan.
# Korak 4: Provedba u route handlerima i server actions (API-je se prvo napada)#
Čak i ako blokirate stranice, napadači će vaše route handlere pozivati direktno. Svaka mutacija treba eksplicitnu autorizacijsku provjeru.
Obrazac: zaštitite route handler s RBAC + ABAC#
Primjer: samo tenant admini mogu pozivati korisnike, i to samo za vlastiti tenant.
// app/api/tenants/[tenantId]/invites/route.ts
import { NextResponse } from "next/server";
import { requireUser } from "@/lib/authz";
import { requireTenantRole } from "@/lib/policies/tenant";
export async function POST(
req: Request,
context: { params: Promise<{ tenantId: string }> }
) {
const session = await requireUser();
const { tenantId } = await context.params;
const membership = await getMembershipByTenantId({
userId: session.userId,
tenantId,
});
requireTenantRole({ userRole: membership?.role ?? null, minRole: "admin" });
const body = await req.json();
const email = String(body.email || "");
const invite = await createInvite({ tenantId, email, invitedBy: session.userId });
return NextResponse.json({ invite }, { status: 201 });
}Obrazac: default je zabrana#
Jednostavan, ali učinkovit stav: ako vaš handler ne pozove require... funkciju, to je bug.
Koristite konvencije imenovanja poput:
requireUserrequireTenantRolerequirePermissionassertCanReadProjectassertCanUpdateInvoice
…i izbjegavajte nejasna imena poput checkAccess.
# Korak 5: Autorizacija provedena u bazi s RLS-om (sloj koji se ne može zaobići)#
Provjere u aplikacijskom sloju su nužne, ali nisu dovoljne ako:
- imate više backendova ili workera
- koristite direktne DB klijente
- ikad krivo konfigurirate API rutu
- uvedete novi upit i zaboravite filter
Row Level Security u PostgreSQL-u je provjeren način da izolaciju tenanta i pravila vlasništva učinite provedivima na razini storagea.
Čest produkcijski setup za SaaS je:
- App kod proslijedi autentificirani user id u DB session
- RLS politike čitaju tu vrijednost i filtriraju retke
Ako ste na Supabaseu, mehanika je dobro dokumentirana, a multitenant obrasce pokrivamo ovdje: Next.js Supabase Row Level Security za multitenant SaaS.
Obrazac RLS politike: izolacija tenanta#
Ovo je konceptualni SQL koji možete prilagoditi. Pretpostavlja da svaki red ima tenant_id i da postoji tablica tenant_memberships.
-- Enable RLS
alter table projects enable row level security;
-- Allow select only if the user is a member of the row's tenant
create policy "projects_select_tenant_members"
on projects for select
using (
exists (
select 1 from tenant_memberships m
where m.tenant_id = projects.tenant_id
and m.user_id = auth.uid()
)
);Obrazac RLS politike: vlasništvo za update#
Primjer: svaki član tenanta može čitati, ali samo owner ili admini mogu ažurirati.
create policy "projects_update_owner_or_admin"
on projects for update
using (
exists (
select 1 from tenant_memberships m
where m.tenant_id = projects.tenant_id
and m.user_id = auth.uid()
and m.role in ('owner', 'admin')
)
)
with check (
projects.tenant_id in (
select m.tenant_id from tenant_memberships m
where m.user_id = auth.uid()
)
);Zašto je RLS važan u brojkama#
U incident retrospektivama, “missing tenant filter” je jedan od najčešćih uzroka SaaS curenja podataka. U tipičnom codebaseu s desecima upita, jedan jedini nescopani upit dovoljan je da izloži sve tenante.
RLS pretvara “sjeti se svugdje dodati where tenant_id = ...” u “baza po defaultu odbija nesigurne upite”.
🎯 Ključna poruka: Ako ste multitenant, izolaciju tenanta provedite u bazi. App provjere poboljšavaju UX i smanjuju load, ali RLS sprječava katastrofalna cross-tenant curenja.
# Obrasci politika koje možete ponovno koristiti (RBAC i ABAC)#
Ispod su česti obrasci koje implementiramo u Next.js projektima kako bi autorizacija bila dosljedna i održiva.
Obrazac 1: Permission stringovi iznad RBAC-a#
Umjesto provjere uloga posvuda, mapirajte uloge na permission stringove poput billing.read i billing.write. To smanjuje “role sprawl”.
| Uloga | Primjer dozvola |
|---|---|
owner | tenant.manage, billing.write, members.write |
admin | tenant.manage, members.write, billing.read |
editor | projects.write, projects.read |
viewer | projects.read |
// lib/policies/permissions.ts
const roleToPermissions: Record<string, string[]> = {
owner: ["tenant.manage", "billing.write", "members.write", "projects.write", "projects.read"],
admin: ["tenant.manage", "billing.read", "members.write", "projects.write", "projects.read"],
editor: ["projects.write", "projects.read"],
viewer: ["projects.read"],
};
export function hasPermission(role: string | null, permission: string) {
if (!role) return false;
return roleToPermissions[role]?.includes(permission) ?? false;
}Obrazac 2: ABAC sa jednom can funkcijom#
Jedna can(user, action, resource, context) funkcija lako se testira i teško se pogrešno koristi.
// lib/policies/can.ts
type Action = "project.read" | "project.update" | "invoice.read";
type User = {
id: string;
tenantRole: "owner" | "admin" | "editor" | "viewer";
tenantId: string;
};
type Project = {
id: string;
tenantId: string;
ownerId: string;
isLocked: boolean;
};
export function can(user: User, action: Action, resource: Project) {
if (user.tenantId !== resource.tenantId) return false;
if (action === "project.read") return true;
if (action === "project.update") {
if (resource.isLocked && user.tenantRole !== "owner") return false;
return user.tenantRole === "owner" || user.tenantRole === "admin" || resource.ownerId === user.id;
}
return false;
}Obrazac 3: Policy wrapperi za pristup podacima#
Umjesto “dohvati pa kasnije provjeri”, wrapperajte upit tako da se politika uvijek primjenjuje.
// lib/data/projects.ts
import { can } from "@/lib/policies/can";
export async function getProjectOrThrow(input: {
user: { id: string; tenantId: string; tenantRole: any };
projectId: string;
}) {
const project = await db.project.findUnique({ where: { id: input.projectId } });
if (!project) throw new Error("NOT_FOUND");
if (!can(input.user, "project.read", project)) throw new Error("FORBIDDEN");
return project;
}To sprječava čestu grešku: vraćanje zapisa pozivatelju i oslanjanje na to da će pozivatelj provjeriti pristup.
Obrazac 4: Odvojite “vidljivost” od “mogućnosti izmjene”#
Mnogi timovi pomiješaju “može čitati” i “može mijenjati” u jednom pravilu. To vodi do pretjeranih ovlasti.
Koristite eksplicitne akcije:
document.readdocument.exportdocument.updatedocument.delete
Kad logirate autorizacijske odluke, logirajte i action string. To čini audit i debug praktičnima.
# RBAC vs ABAC: česte greške i kako ih izbjeći#
Greška 1: Korištenje RBAC uloga za sve#
Ako stalno dodajete uloge da biste izrazili kontekst, RBAC postaje neodrživ. Primjeri:
tenant_123_admineu_editorenterprise_billing_viewer
To eksponencijalno raste i usporava isporuku.
Rješenje: držite uloge stabilnima, a kontekst prebacite u ABAC atribute poput tenantId, region, plan.
Greška 2: Povjerenje u klijent za autorizaciju#
Skrivanje UI elemenata nije autorizacija. Napadači direktno zovu API.
Rješenje: provedite autorizaciju u server kodu i bazi. UI provjere tretirajte samo kao praktičnu pomoć.
Greška 3: Stavljanje teške autorizacijske logike u middleware#
Middleware je odličan za route matching, ali ne i za evaluaciju politika koja zahtijeva DB. Povećava latenciju na svakom requestu i teže ga je debuggati.
Rješenje: middleware neka bude “plitak”, a provjere resursa prebacite u server komponente i route handlere, dok invarijante provodite RLS-om.
Greška 4: Zaboravljanje “list endpointa” i pretrage#
Čak i ako zaštitite detail stranicu, list endpointi često cure podatke. Jedan “search all projects” upit bez tenant scoping-a dovoljan je.
Rješenje: primijenite tenant scoping u bazi (RLS) i također u app upitima radi defense in depth.
# Praktičan checklist implementacije (App Router)#
Koristite ovo kao minimalan rollout plan.
| Sloj | Što implementirati | Gotovo kad |
|---|---|---|
| Middleware | Redirect neautentificiranih korisnika, blokada očitih admin ruta | Zaštićene sekcije nisu dostupne preko URL-a |
| Server Components | requireUser, requireRole, requireTenantRole | Neautorizirane stranice padaju prije renderiranja podataka |
| Route Handlers | Eksplicitne require... provjere na svakoj mutaciji | API se ne može pozvati bez ispravne uloge ili članstva |
| Database | RLS politike za izolaciju tenanta i vlasništvo | Cross-tenant čitanja su nemoguća čak i uz bugovite upite |
| Logging | Logirajte odbijene odluke s action i tenant | 403 probleme možete brzo debuggati |
| Testing | Unit test can politika, API testovi za 403 i 404 | Promjene politika ne uvode regresije |
# Ključne poruke#
- Koristite RBAC za grubi pristup poput admin područja, a ABAC za pravila na razini resursa poput granica tenanta, vlasništva i ograničenja plana.
- Middleware tretirajte kao ranu prepreku, ne kao izvor istine. Stvarnu autorizaciju provodite u server komponentama, route handlerima i server actions.
- Za multitenant SaaS, učinite izolaciju tenanta invarijantom baze pomoću RLS-a, ne “konvencijom da se sjetimo filtrirati”.
- Centralizirajte pravila u ponovno iskoristive policy funkcije poput
requireTenantRoleican(user, action, resource)kako biste spriječili drift između stranica i API-ja. - Preferirajte deny by default: svaki osjetljiv handler mora pozvati
require...guard, a svaka tablica mora imati RLS priču.
# Zaključak#
Next.js App Router vam daje alate da autorizaciju napravite ispravno: middleware za rane routing odluke, server-side guardove za provedbu i DB politike za jamstva koja se ne mogu zaobići.
Ako želite pomoć oko dizajna policy modela, sigurne implementacije RLS-a ili refaktora neurednog RBAC setupa u održiv RBAC + ABAC, Samioda može pregledati vaš postojeći codebase i brzo isporučiti ojačani autorizacijski sloj. Krenite tako da uskladite svoj auth stack i claimove, a zatim zaključajte tenant granice na razini baze.
FAQ
Osnivač i senior developer u Samiodi. 8+ godina iskustva u izradi React, Next.js, Flutter i n8n rješenja za klijente diljem Europe.
Više iz kategorije Web razvoj
Sve →Strategija testiranja Reacta u 2026.: Vitest + React Testing Library + MSW za sigurne releaseove
Pragmatična strategija testiranja React aplikacija u 2026. uz Vitest, React Testing Library i MSW. Naučite realističnu testnu piramidu, smanjite flaky testove i isporučujte s pouzdanjem uz CI-spremne obrasce.
Moderna React frontend arhitektura: moduli po značajkama, granice i skalabilnost
Praktičan vodič za React frontend arhitekturu temeljenu na modulima po značajkama: jasne granice, zajednički slojevi, pravila ovisnosti i održiva strategija strukture mapa za React i Next.js.
Observabilnost za Next.js App Router u 2026.: Sentry, OpenTelemetry, traceovi i korisna upozorenja
End-to-end postavke za Next.js logiranje, nadzor i tracing sa Sentryjem i OpenTelemetryjem kroz Server Actions, Route Handlers i razliku Edge naspram Node runtimea—uz dashboarde, pragove upozorenja i korelaciju sesije s traceom.
Trebate pomoć s projektom?
Gradimo prilagođena rješenja koristeći tehnologije iz ovog članka. Senior tim, fiksne cijene.
Povezani članci
Next.js + Supabase RLS za multi‑tenant SaaS: pravila, uloge i siguran pristup podacima
Praktičan vodič za Next.js App Router i Supabase Row Level Security za multi-tenant SaaS: dizajn tablica, pravila, uloge, obrasci pristupa na serveru, česte zamke i kontrolna lista za deployment.
Next.js + Supabase SaaS početna arhitektura (App Router): Auth, RLS, naplata i multi-tenancy
Production-ready nacrt za Next.js App Router + Supabase SaaS početnu arhitekturu: autentikacija, Postgres podatkovni model, RLS politike, Stripe naplata i multi-tenant dizajn organizacija s konkretnim primjerima.
Next.js multitenant SaaS arhitektura: modeli tenancije, rutiranje, autentikacija i izolacija podataka (Vodič za 2026.)
Praktičan vodič za Next.js multitenant SaaS arhitekturu: modeli tenancije, tenant-aware rutiranje uz App Router i middleware, obrasci autentikacije te učvršćivanje izolacije podataka kako bi se spriječila curenja.