одGabriel L. Manor
Supabase olakšava dodavanje autentifikacije u vašu aplikaciju uz ugrađenu podršku za e-poštu, OAuth i magične veze, ali dok Supabase Auth upravlja ko su vaši korisnici, često vam je potreban i autorizacioni sloj.
Supabase nudi odličan backend sa ugrađenim auth i Row Level Security (RLS), upravljanjemfine-grained permissionsПосебно оне засноване наrelationships between users and dataDaleko je od lakoće.
Možda želite da ograničite radnje kao što su uređivanje ili brisanje podataka vlasnicima resursa, da sprečite korisnike da glasaju za svoj sadržaj ili da primenite različite dozvole za različite uloge korisnika.
Овај туториал пролази кроз како имплементиратиSupabase authentication and authorizationу аNext.jsапликације .
We'll start with Supabase Authза логин и управљање сесијом, а затим додајтеauthorization rulesКоришћењеKontrola pristupa zasnovanog na vezama (ReBAC)Наметнута крозSupabase Edge Functionsи аlocal Policy Decision Point (PDP). .
На крају, имаћете апликацију за заједничко гласање у реалном времену која подржава и јавне и заштићене акције - и флексибилан систем овлашћења који се може еволуирати како ваша апликација расте.
Šta mi gradimo
У овом водичу, ми ћемо изградити апликацију за гласање у реалном времену користећиSupabaseиNext.jsТо показује и аутентификацију и овлашћење у акцији.
Апликација омогућава корисницима да креирају анкете, гласају за друге и управљају само својим садржајем.Supabase Authza login/signup i kako izvršitiauthorization policiesкоји контролише ко може гласати, уредити или избрисати.
Користићемо суштинске карактеристике Супасеа -Auth,Postgres,RLS,Realtime, иEdge FunctionsУ комбинацији са аRelationship-Based Access Control (ReBAC)Модел за спровођење правила приступа по кориснику и по ресурсу.
Технички стацк
- Supabase – Backend-as-a-service za bazu podataka, autentifikaciju, funkcije u realnom vremenu i funkcije Edge
- Next.js – Фронтенд оквир за изградњу апликације УИ и АПИ руте
- Permit.io – (за РеБАЦ) да дефинише и процени логику овлашћења преко ПДП-а
- Субасе ЦЛИ – За управљање и распоређивање Едге Функција локално и у производњи
Предуслови
- Node.js је инсталиран
- База рачуна
- Дозволите.io налог
- Упознавање са React/Next.js
- Početak projekta Repo
Šta ova aplikacija može da uradi?
Демо апликација је платформа за гласање у реалном времену изграђена са Next.js и Supabase, где корисници могу креирати анкете и гласати за друге.
- Сваки корисник (аутентификован или не) може да прегледа листу јавних истраживања
- Само аутентични корисници могу креирати анкете и гласати
- Корисник не може гласати на анкети коју је креирао
- Samo kreator ankete može da ga uređuje ili briše
Tutorial Pregled
Пратићемо ове опште кораке:
- Подешавање пројекта Субасе, шеме, аутх и РЛС
- Изградите основне функције апликације као што су креирање анкета и гласање
- Правила о одобрењу модела дефинишу улоге и правила у Permit.io
- Креирајте функције Субасе Едге за синхронизацију корисника, додељивање улога и проверавање дозвола
- Имплементирати политике у апликацији фронтенд користећи те функције ивице
Hajde da počnemo -
Setting up Supabase in the Project
Postavljanje supbaze u projektuОпционално: Clone the Starter Template
I've already created a Почетак ХрамајеGitHubса свим кодом који вам је потребан да бисте започели тако да се можемо усредсредити на имплементацију Субасе и Пермит.ио.
Projekat možete klonirati tako što ćete pokrenuti sledeću komandu:
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
Након што сте клонирали пројекат, пређите у директоријум пројекта и инсталирајте зависности:
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Creating a new Project in Supabase
Да бисте започели:
- Идите на https://supabase.com и пријавите се или креирајте налог.
- Кликните на "Нови пројекат" и попуните име пројекта, лозинку и регион.
- Jednom kada se kreira, idite na Project Settings → API i zapišite svoj Project URL i Anon Key – potrebni su vam kasnije.
Подешавање аутентификације и базе података у Субаси
Користићемо уграђену е-пошту / лозинку аутх Супабесе:
- У бочној траци идите на аутентификацију → провајдери
- Омогућите провајдера е-поште
- (Opcionalno) Deaktivirajte potvrdu e-pošte za testiranje, ali držite je uključenu za proizvodnju
Креирање шеме базе података
Ова апликација користи три главне табеле:polls
,options
, иvotes
• КориститеSQL Editoru Supabase Dashboard i pokrenite sledeće:
-- Create a polls table
CREATE TABLE polls (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
question TEXT NOT NULL,
created_by UUID REFERENCES auth.users(id) NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
creator_name TEXT NOT NULL,
expires_at TIMESTAMP WITH TIME ZONE NOT NULL,
);
-- Create an options table
CREATE TABLE options (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
poll_id UUID REFERENCES polls(id) ON DELETE CASCADE,
text TEXT NOT NULL,
);
-- Create a votes table
CREATE TABLE votes (
id UUID DEFAULT uuid_generate_v4() PRIMARY KEY,
poll_id UUID REFERENCES polls(id) ON DELETE CASCADE,
option_id UUID REFERENCES options(id) ON DELETE CASCADE,
user_id UUID REFERENCES auth.users(id),
created_at TIMESTAMP WITH TIME ZONE DEFAULT TIMEZONE('utc', NOW()),
UNIQUE(poll_id, user_id)
);
Omogućavanje bezbednosti na nivou linije (RLS)
омогућаваРЛСза сваку табелу и дефинисати политике:
-- Polls policies
ALTER TABLE polls ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view polls" ON polls
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can create polls" ON polls
FOR INSERT TO authenticated
WITH CHECK (auth.uid() = created_by);
-- Options policies
ALTER TABLE options ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view options" ON options
FOR SELECT USING (true);
CREATE POLICY "Poll creators can add options" ON options
FOR INSERT TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM polls
WHERE id = options.poll_id
AND created_by = auth.uid()
)
);
-- Votes policies
ALTER TABLE votes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view votes" ON votes
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can vote once" ON votes
FOR INSERT TO authenticated
WITH CHECK (
auth.uid() = user_id AND
NOT EXISTS (
SELECT 1 FROM polls
WHERE id = votes.poll_id
AND created_by = auth.uid()
)
);
Да бисте користили функције Супабесе у реалном времену:
- У бочној траци, идите на Табела Едитор
- За сваку од три табеле (испитивања, опције, гласови): Кликните на три тачке → Еди Табела Toggle "Омогући Реално време" Сачувајте промене
Implementacija Supabase Email Authentication u aplikaciji
У овој демо апликацији, свако може да прегледа листу анкета доступних на апликацији, и активних и истекалих. Да бисте прегледали детаље анкете, управљали или гласали за било коју анкету, корисник мора бити пријављен. Ми ћемо користити е-пошту и лозинку као средство аутентификације за овај пројекат. У вашем пројекту Next.js чувајте своје поверења Субасе у.env.local
• :
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
Ажурирајте компоненту за пријављивање да бисте управљали и регистрацијом и пријављивањем путем е-поште / лозинке:
import { useState } from "react";
import { createClient } from "@/utils/supabase/component";
const LogInButton = () => {
const supabase = createClient();
async function logIn() {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
} else {
setShowModal(false);
}
}
async function signUp() {
const { error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
user_name: userName,
},
},
});
if (error) {
setError(error.message);
} else {
setShowModal(false);
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (isLogin) {
await logIn();
} else {
await signUp();
}
};
return (
<>
<button
onClick={() => setShowModal(true)}
className="flex items-center gap-2 p-2 bg-gray-800 text-white rounded-md">
Log In
</button>
...
</>
);
};
export default LogInButton;
Овде, ми користимо Супбасе'сsignInWithPassword
метод за пријављивање у корисника иsignUp
метод за пријављивање новог корисника са њиховом е-поштом и лозинком. Такође чувамо корисничко име уuser_name
Поље у метаподацима корисника.
You can also use supabase.auth.signOut()
да се пријавите за кориснике и преусмерите их:
import { createClient } from "@/utils/supabase/component";
import { useRouter } from "next/router";
const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => {
const router = useRouter();
const supabase = createClient();
const handleLogOut = async () => {
await supabase.auth.signOut();
closeDropdown();
router.push("/");
};
return (
...
);
};
export default LogOutButton;
Овде, ми користимоsignOut
метод из Субасе да се пријавите корисника и преусмери их на почетну страницу.
Слушање за промене у статусу аутентификације корисника
Слушање промена у статусу аутентификације корисника омогућава нам да ажурирамо УИ на основу статуса аутентификације корисника.
- Прикажи / сакриј елементе корисничког интерфејса као што су дугмад за пријављивање / пријављивање
- Conditionally restrict access to protected pages (like voting or managing polls)
- Обезбедите да само аутентификовани корисници могу да обављају ограничене радње
Mi ćemo koristitisupabase.auth.onAuthStateChange()
да слушате ове догађаје и ажурирате апликацију у складу с тим.
In the Layout.tsx
file: Track Global Auth State
import React, { useEffect, useState } from "react";
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const Layout = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const fetchUser = async () => {
const supabase = createClient();
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
return (
...
);
};
export default Layout;
Ограничити приступ заштићеним страницама
На страницама каоpoll details or poll management, you should also listen for authentication state changes to prevent unauthenticated users from accessing them.
Evo kako izgleda upages/polls/[id].tsx
:
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const Page = () => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const fetchUser = async () => {
const supabase = createClient();
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
setLoading(false);
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
return (
...
);
export default Page;
Сличан модел се примењује уpages/polls/manage.tsx
, где корисници треба да виде своје анкете само ако су пријављени:
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const Page = () => {
const [user, setUser] = useState<User | null>(null);
const supabase = createClient();
useEffect(() => {
const fetchUser = async () => {
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
if (!session?.user) {
setLoading(false);
}
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
return (
...
);
};
export default Page;
Ови обрасци осигуравају да ваш кориснички интерфејс одражава тренутни статус аутентификације корисника и да представља основу за провере овлашћења које ћемо додати касније.user
object when calling the checkPermission
Edge Function to determine whether a user is allowed to vote or manage a specific poll.
Изградња функционалности апликације Polling
With Supabase configured and authentication working, we can now build the core functionality of the polling app. In this section, we’ll cover:
- Креирање нових анкета
- Претраживање и приказивање анкета у реалном времену
- Uvođenje sistema glasanja
Ово нам даје основно понашање апликација које ћемо ускоро заштитити са фино зрнатим дозволама.
Креирање нових анкета
Корисници морају бити пријављени да би креирали анкете. Свака анкета укључује питање, датум истека и скуп опција. Такође снимамо ко је креирао анкету тако да касније можемо користити ту везу за контролу приступа.
unutraNewPoll.tsx
, преузмите аутентификованог корисника и користите Субасе да бисте убацили анкету и његове опције:
import React, { useEffect, useState } from "react";
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const NewPoll = () => {
const [user, setUser] = useState<User | null>(null);
const supabase = createClient();
useEffect(() => {
const fetchUser = async () => {
const supabase = createClient();
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (question.trim() && options.filter(opt => opt.trim()).length < 2) {
setErrorMessage("Please provide a question and at least two options.");
return;
}
// Create the poll
const { data: poll, error: pollError } = await supabase
.from("polls")
.insert({
question,
expires_at: new Date(expiryDate).toISOString(),
created_by: user?.id,
creator_name: user?.user_metadata?.user_name,
})
.select()
.single();
if (pollError) {
console.error("Error creating poll:", pollError);
setErrorMessage(pollError.message);
return;
}
// Create the options
const { error: optionsError } = await supabase.from("options").insert(
options
.filter(opt => opt.trim())
.map(text => ({
poll_id: poll.id,
text,
}))
);
if (!optionsError) {
setSuccessMessage("Poll created successfully!");
handleCancel();
} else {
console.error("Error creating options:", optionsError);
setErrorMessage(optionsError.message);
}
};
return (
...
);
};
export default NewPoll;
Касније ћемо позвати функцију Едге овде да бисмо доделили улогу "креатора" у Permit.io.
Fetching and Displaying Polls
Polls are divided into active (not yet expired) and pastМожете их преузети помоћу Субасе упита филтрираних по тренутној временској ознаци и поставити претплате у реалном времену да одражавају промене одмах.
Пример изpages/index.tsx
:
import { PollProps } from "@/helpers";
import { createClient } from "@/utils/supabase/component";
export default function Home() {
const supabase = createClient();
useEffect(() => {
const fetchPolls = async () => {
setLoading(true);
const now = new Date().toISOString();
try {
// Fetch active polls
const { data: activePolls, error: activeError } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.gte("expires_at", now)
.order("created_at", { ascending: false });
if (activeError) {
console.error("Error fetching active polls:", activeError);
return;
}
// Fetch past polls
const { data: expiredPolls, error: pastError } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.lt("expires_at", now)
.order("created_at", { ascending: false });
if (pastError) {
console.error("Error fetching past polls:", pastError);
return;
}
setCurrentPolls(activePolls);
setPastPolls(expiredPolls);
} catch (error) {
console.error("Unexpected error fetching polls:", error);
} finally {
setLoading(false);
}
};
fetchPolls();
// Set up real-time subscription on the polls table:
const channel = supabase
.channel("polls")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "polls",
},
fetchPolls
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
return (
...
);
}
Viewing and Managing User Polls
Here, we are fetching active and past polls from the polls
Табела у Субаси. Такође постављамо претплату у реалном времену да бисмо слушали промене уpolls
table so that we can update the UI with the latest poll data. To differentiate between active and past polls, we are comparing the expiry date of each poll with the current date.
Ажурирање наpages/manage.tsx
страница за преузимање и приказивање само анкета које је креирао корисник:
import { PollProps } from "@/helpers";
const Page = () => {
useEffect(() => {
if (!user?.id) return;
const fetchPolls = async () => {
try {
const { data, error } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.eq("created_by", user.id)
.order("created_at", { ascending: false });
if (error) {
console.error("Error fetching polls:", error);
return;
}
setPolls(data || []);
} catch (error) {
console.error("Unexpected error fetching polls:", error);
} finally {
setLoading(false);
}
};
fetchPolls();
// Set up real-time subscription
const channel = supabase
.channel(`polls_${user.id}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "polls",
filter: `created_by=eq.${user.id}`,
},
fetchPolls
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [user]);
return (
...
);
};
export default Page;
Овде добијамо само анкете које је креирао корисник и слушамо ажурирања у реалном времену уpolls
Табела тако да је кориснички интерфејс ажуриран са најновијим подацима анкете.
Такође, ажурирајтеPollCard
компоненту тако да ако је пријављени корисник креатор анкете, иконе за уређивање и брисање анкете ће им се приказати на анкети.
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const PollCard = ({ poll }: { poll: PollProps }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const supabase = createClient();
const fetchUser = async () => {
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
setLoading(false);
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
return (
...
)}
</Link>
);
};
export default PollCard;
Дакле, сада, на картици анкете, ако је пријављени корисник креатор анкете, иконе за уређивање и брисање анкете ће им се приказати.
Имплементација система гласања
Логика гласања спроводи:
- Only one vote per user per poll
- Kreatori ne mogu da glasaju na sopstvenim anketama
- Votes are stored in the
votes
table - Rezultati se prikazuju i ažuriraju u realnom vremenu
Хајде да разбијемо како то функционише уViewPoll.tsx
Компоненте :
Fetch the Logged-In UserПотребан нам је идентификатор тренутног корисника да бисмо утврдили право гласа и снимили њихов глас.
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const ViewPoll = () => {
const [user, setUser] = useState<User | null>(null);
const supabase = createClient();
useEffect(()
const fetchUser = async () => {
const {
data: { user },
} = await supabase.auth.getUser();
setUser(user);
};
fetchUser();
}, []);
Load Poll Details and Check Voting StatusКада имамо корисника, добијамо:
- Сама анкета (укључујући опције и бројање гласова)
- Da li je ovaj korisnik već glasao
Такође их поново позивамо касније у ажурирањима у реалном времену.
useEffect(() => {
if (!user) {
return;
}
const checkUserVote = async () => {
const { data: votes } = await supabase
.from("votes")
.select("id")
.eq("poll_id", query.id)
.eq("user_id", user.id)
.single();
setHasVoted(!!votes);
setVoteLoading(false);
};
const fetchPoll = async () => {
const { data } = await supabase
.from("polls")
.select(
`
*,
options (
id,
text,
votes (count)
)
`
)
.eq("id", query.id)
.single();
setPoll(data);
setPollLoading(false);
checkUserVote();
};
fetchPoll();
Listen for Real-Time Updates
Прихватамо промене уvotes
Табела, намењена овом гласању.Када се гласа ново гласање, добијамо ажуриране податке о гласању и статус гласања.
const channel = supabase
.channel(`poll-${query.id}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "votes",
filter: `poll_id=eq.${query.id}`,
},
() => {
fetchPoll();
checkUserVote();
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [query.id, user]);
Handle the Vote Submission
If the user hasn’t voted and is allowed to vote (we’ll add a permission check later), we insert their vote.
const handleVote = async (optionId: string) => {
if (!user) return;
try {
const { error } = await supabase.from("votes").insert({
poll_id: query.id,
option_id: optionId,
user_id: user.id,
});
if (!error) {
setHasVoted(true);
}
} catch (error) {
console.error("Error voting:", error);
}
};
Display the Poll ResultsИзрачунавамо укупан број гласова и одбројавање до времена истека. Затим можете да користите ово да бисте приказали шипке напретка или статистику.
if (!poll || pollLoading || voteLoading) return <div>Loading...</div>;
// 6. calculate total votes
const totalVotes = calculateTotalVotes(poll.options);
const countdown = getCountdown(poll.expires_at);
return (
...
);
};
export default ViewPoll;
Са овим подешавањем, ваш систем за гласање је у потпуности функционалан. Али тренутно, свако ко је пријављен може технички покушати да гласа – чак и на сопственој анкети.authorization checks using Permit.ioиSupabase Edge Functionsda bi se ta pravila primenjivala.
Пре него што то урадимо, прво погледајмо тип слоја овлашћења који ћемо имплементирати.
Understanding ReBAC (Relationship-Based Access Control)
Supabase handles authentication and basic row-level permissions well, but it doesn’t support complex rules like:
- Preventing users from voting on their own polls
- Додјељивање улога по ресурсима (као што је "креатор" за одређену анкету)
- Managing access via external policies
To support these kinds of relationship-based permissions, we’ll implement ReBAC with Permit.io.
Relationship-Based Access Control (ReBAC)je model za upravljanje dozvolama na osnovu odnosa između korisnika i resursa. Umesto da se oslanja isključivo na uloge ili atribute (kao u RBAC ili ABAC), ReBAC određuje pristup tako što procenjuje kako je korisnik povezan sa resursom koji pokušavaju da pristupe.
Relationship-Based Access Control (ReBAC)
У овом туторијалу, примењујемо РеБАЦ на апликацију за гласање:
- Корисник који је креирао анкету је једини који може да га управља (уреди / избрише)
- Корисник не може гласати на сопственој анкети
- Остали аутентификовани корисници могу гласати једном по анкети
Моделирањем ових односа у Permit.io-у можемо дефинисати фино зрнана правила приступа која превазилазе уграђену сигурност нивоа рова (РЛС) Супабасе-а.
За више о РеБАЦ-у, погледајтеPermit.io’s ReBAC docs.
Access Control Design
За нашу апликацију за гласање дефинишемо:
- Један ресурс са акцијама специфичним за ресурсе: анкете: креирање, читање, брисање, ажурирање.
- Dve uloge za dodelu nivoa dozvola na osnovu odnosa korisnika sa resursima: autentični: Može da izvrši radnje za kreiranje i čitanje u anketama. Ne može da izbriše ili ažurira radnje u anketama. kreator: Može da kreira, čita, briše i ažurira radnje u anketama. Može da izvrši radnje za čitanje i kreiranje u anketama. Ne može da koristi radnje za kreiranje u svojim anketama.
Подешавање дозволе.io
Хајде да прођемо кроз подешавање модела овлашћења у Дозвола.
- Креирајте нови пројекат у Permit.io Име га нешто попут supabase-polling
- Дефинисати ресурс истраживања Иди на картицу Политика → Ресурси Кликните на "Креирај ресурс" Именовање истраживања и додајте акције: прочитати, креирати, ажурирати, избрисати
- Омогућавање РеБАЦ-а за ресурс У одељку „РеБАЦ опције“ дефинишите следеће улоге: аутентификовани креатор Кликните на Сачувај
Пређите на картицу "Улоге" да бисте видели улоге из ресурса које смо управо креирали. Имајте на уму да је дозвола створила подразумеване улоге (admin
, editor
, user
) који су непотребни за овај туториал.
- Дефинисати политике приступа Идите на картицу Политике → Политике Користите визуелну матрицу да бисте дефинисали: аутентификовани може да чита и креира анкете креатор може да чита, ажурира и брише анкете (опционално) Можете да конфигуришете дозволе за гласање као део овог или преко другог ресурса ако моделирате гласање одвојено
-
Add resource instances
-
Go to Directory → Instances
-
Add individual poll IDs as resource instances (you’ll automate this later when new polls are created)
-
Assign roles to users per poll (e.g.
user123
iscreator
ofpoll456
)
-
Ова структура нам даје моћ да напишемо флексибилна правила приступа и спроводимо их по кориснику, по анкети.
Сада када смо завршили почетну инсталацију на тањиру Дозволи, хајде да га користимо у нашој апликацији.Дозволите мина наш Субасе пројекат преко Едге Функције које:
- Синхронизација нових корисника
- Assign creator roles
- Check access on demand
Setting up Permit in the Polling Application
Permit offers multiple ways to integrate with your application, but we'll use the Container PDP for this tutorial. You have to host the container online to access it in Supabase Edge functions. You can use services like railway.comКада сте га хостовали, сачувајте URL за ваш контејнер.
Добијте API кључ за дозволу тако што ћете кликнути на "Пројекти" у приборној траци за дозволу, прећи на пројекат на којем радите, кликнути на три тачке и одабрати "Копирај API кључ".
Creating Supabase Edge Function APIs for Authorization
Supabase Edge Functionsсавршени су за интеграцију услуга треће стране као што је Permit.io. користићемо их за спровођење наших правила РеБАЦ-а у тренутку покретања проверавањем да ли корисницима је дозвољено да обављају одређене акције на анкетама.
Create Functions in Supabase
Иницијализујте Субасе у свом пројекту и креирајте три различите функције користећиsupabase functions new
Ово ће бити почетна тачка за ваше функције:
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
Ово ће створити аfunctions
Folder usupabase
папка заједно са крајњим тачкама. Сада, хајде да напишемо кодове за сваку крајњу тачку.
Препоручује се да се узимају у обзир упутства за употребу (syncUser.ts
)
Ova funkcija sluša za Supabase’sSIGNED_UP
Када се нови корисник региструје, синхронизујемо њихов идентитет са Permit.io и доделимо им подразумеваниauthenticated
улоге
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { Permit } from "npm:permitio";
const corsHeaders = {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
}
// Supabase Edge Function to sync new users with Permit.io
Deno.serve(async (req) => {
const permit = new Permit({
token: Deno.env.get("PERMIT_API_KEY"),
pdp: "<https://real-time-polling-app-production.up.railway.app>",
});
try {
const { event, user } = await req.json();
// Only proceed if the event type is "SIGNED_UP"
if (event === "SIGNED_UP" && user) {
const newUser = {
key: user.id,
email: user.email,
name: user.user_metadata?.name || "Someone",
};
// Sync the user to Permit.io
await permit.api.createUser(newUser);
await permit.api.assignRole({
role: "authenticated",
tenant: "default",
user: user.id,
});
console.log(`User ${user.email} synced to Permit.io successfully.`);
}
// Return success response
return new Response(
JSON.stringify({ message: "User synced successfully!" }),
{ status: 200, headers: corsHeaders },
);
} catch (error) {
console.error("Error syncing user to Permit: ", error);
return new Response(
JSON.stringify({
message: "Error syncing user to Permit.",
"error": error
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
});
Додељивање улоге креатора (updateCreatorRole.ts
)
Када корисник креира анкету, ова функција се позива на:
- Синхронизовати анкету као нову Permit.io инстанцу ресурса
-
Assign the user the
creator
role for that poll
import "jsr:@supabase/functions-js/edge-runtime.d.ts"; import { Permit } from "npm:permitio"; const corsHeaders = { 'Access-Control-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', } Deno.serve(async (req) => { const permit = new Permit({ token: Deno.env.get("PERMIT_API_KEY"), pdp: "<https://real-time-polling-app-production.up.railway.app>", }); try { const { userId, pollId } = await req.json(); // Validate input parameters if (!userId || !pollId) { return new Response( JSON.stringify({ error: "Missing required parameters." }), { status: 400, headers: { "Content-Type": "application/json" } }, ); } // Sync the resource (poll) to Permit.io await permit.api.syncResource({ type: "polls", key: pollId, tenant: "default", attributes: { createdBy: userId } }); // Assign the creator role to the user for this specific poll await permit.api.assignRole({ role: "creator", tenant: "default", user: userId, resource: { type: "polls", key: pollId, } }); return new Response( JSON.stringify({ message: "Creator role assigned successfully", success: true }), { status: 200, headers: corsHeaders }, ); } catch (error) { console.error("Error assigning creator role: ", error); return new Response( JSON.stringify({ message: "Error occurred while assigning creator role.", error: error }), { status: 500, headers: { "Content-Type": "application/json" } }, ); } });
Провера дозвола (checkPermission.ts
)
Ова функција делује као врата - проверава да ли је кориснику дозвољено да изврши одређену акцију (create
,read
, update
,delete
) на одређеном анкету.
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { Permit } from "npm:permitio";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"Authorization, x-client-info, apikey, Content-Type",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
};
Deno.serve(async req => {
const permit = new Permit({
token: Deno.env.get("PERMIT_API_KEY"),
pdp: "<https://real-time-polling-app-production.up.railway.app>",
});
try {
const { userId, operation, key } = await req.json();
// Validate input parameters
if (!userId || !operation || !key) {
return new Response(
JSON.stringify({ error: "Missing required parameters." }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
// Check permissions using Permit's ReBAC
const permitted = await permit.check(userId, operation, {
type: "polls",
key,
tenant: "default",
// Include any additional attributes that Permit needs for relationship checking
attributes: {
createdBy: userId, // This will be used in Permit's policy rules
},
});
return new Response(JSON.stringify({ permitted }), {
status: 200,
headers: corsHeaders,
});
} catch (error) {
console.error("Error checking user permission: ", error);
return new Response(
JSON.stringify({
message: "Error occurred while checking user permission.",
error: error,
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
});
Локални тестови
Покрените сервер Supabase dev да бисте тестирали функције локално:
npx supabase start
npx supabase functions serve
Tada možete da dodirnete svoje funkcije na:
<http://localhost:54321/functions/v1/><function-name>
Пример :
<http://localhost:54321/functions/v1/checkPermission>
Integrating Authorization Checks in the UI
Сада када смо креирали нашу логику овлашћења са Permit.io и изложили је преко Субасе Едге Функције, време је да се оне провере спроведу унутар компоненти апликације.
У овом одељку, ажурираћемо кључне компоненте корисничког интерфејса како бисмо позвали те функције и условно дозволили или блокирали радње корисника као што је гласање или управљање анкетама на основу провера дозвола.
NewPoll.tsx
: Доделити улогу креатора након креирања истраживања
Претраживање.tsx
Након што креирате анкету и сачувате је на Субаси, зовемоupdateCreatorRole
function to:
- Синхронизовати ново истраживање као ресурс у Permit.io
- Потврдите да је корисник креирао тренутну улогу креирача за ову конкретну анкету.Поставите питање и најмање две опције.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понекад покушајте да креирате анкету.Понека
ViewPoll.tsx
Ограничење гласања на основу дозвола
Пре него што кориснику дозволимо да гласа на анкети, позивамоcheckPermission
функција да провери да имајуcreate
Дозвола наvotes
Ресурс. ево како се примењује правило:“A creator cannot vote on their own poll.”
Check voting permission:
const [canVote, setCanVote] = useState(false);
useEffect(() => {
const checkPermission = async () => {
if (!user || !query.id) return;
try {
const response = await fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "create",
key: query.id,
}),
});
const { permitted } = await response.json();
setCanVote(permitted);
} catch (error) {
console.error("Error checking permission:", error);
setCanVote(false);
}
};
checkPermission();
}, [user, query.id]);
Disable vote buttons if user isn’t allowed:
<button
onClick={() => handleVote(option.id)}
disabled={!user || !canVote}}
className="w-full text-left p-4 rounded-md hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
{option.text}
</button>
Show a message if the user is not allowed to vote:
{user && !canVote && (
<p className="mt-4 text-gray-600">You cannot vote on your own poll</p>
)}
PollCard.tsx
: Контрола приступа за уређивање / брисање
Такође ограничавамо акције управљања анкетама (уређивање и брисање) проверавајући да ли корисник имаupdate
илиdelete
дозволу за ову анкету.
Check management permissions:
const [canManagePoll, setCanManagePoll] = useState(false);
useEffect(() => {
const checkPollPermissions = async () => {
if (!user || !poll.id) return;
try {
// Check for both edit and delete permissions
const [editResponse, deleteResponse] = await Promise.all([
fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "update",
key: poll.id,
}),
}),
fetch("/api/checkPermission", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "delete",
key: poll.id,
}),
}),
]);
const [{ permitted: canEdit }, { permitted: canDelete }] =
await Promise.all([editResponse.json(), deleteResponse.json()]);
// User can manage poll if they have either edit or delete permission
setCanManagePoll(canEdit || canDelete);
} catch (error) {
console.error("Error checking permissions:", error);
setCanManagePoll(false);
}
};
checkPollPermissions();
}, [user, poll.id]);
Conditionally show management buttons:
Замените га:
{user?.id === poll?.created_by && (
са :
{canManagePoll && (
<div className="flex justify-start gap-4 mt-4">
<button type="button" onClick={handleEdit}>
</button>
<button type="button" onClick={handleDelete}>
</button>
</div>
)}
Тестирање интеграције
Када сте интегрисани, требало би да видите следеће понашање у апликацији:
- Isključeni korisnici mogu da gledaju ankete, ali ne i da interaktuju
- Аутентификовани корисници могу гласати за анкете које нису креирали
- Kreatori ne mogu da glasaju na sopstvenim anketama
- Само креатори виде опције за уређивање / брисање у својим анкетама
На почетном екрану, корисници могу да виде листу активних и прошлих анкета, без обзира да ли су пријављени или не. Међутим, када кликну на анкету, неће моћи да виде детаље анкете или гласају за њу.
Када се пријавите, корисник може да прегледа детаље анкете и гласа на њој. Међутим, ако је корисник креатор анкете, неће моћи да гласају на њој.
Закључак
In this tutorial, we explored how to implement Supabase authentication and authorizationU realnom svetuNext.js application.
Почели смо са постављањемSupabase Authза пријављивање и пријављивање, креирао је релациона шему са заштитом нивоа рова и додао динамичку логику овлашћења користећиReBACУз помоћSupabase Edge Functionsи аPolicy Decision Point (PDP), спровели смо дозволе за проверу директно са фронта.
КомбинујућиSupabase AuthСа флексибилном контролом приступа, могли смо:
- Аутентификовање корисника путем е-поште и лозинке
- Ограничити управљање гласањем и анкетама овлашћеним корисницима
- Спречити креаторе да гласају за своје анкете
- Dodelite i ocenite uloge korisnika na osnovu odnosa sa podacima
This setup gives you a scalable foundation for building apps that require both authentication and fine-grained authorization.
Dalje čitanje
- Permit.io Ребац Водич
- Дозвола + провајдер аутентификације
- Елементи дозвола: Уграђени кориснички интерфејс за управљање улогама
- Филтрирање података са дозволом
-
Imate pitanja? pridružite nam seСлаба заједница, где стотине програмера граде и разговарају о овлашћењу.
Слаба заједница