zaGabriel L. Manor
Supabase omogućuje jednostavno dodavanje autentifikacije vašoj aplikaciji uz ugrađenu podršku za e-poštu, OAuth i čarobne veze.
Supabase nudi odličan backend s ugrađenim auth i Row Level Security (RLS), upravljanjemfine-grained permissionsPogotovo one koje se temelje narelationships between users and dataDaleko je od lakoće.
Možda želite ograničiti radnje kao što su uređivanje ili brisanje podataka vlasnicima resursa, spriječiti korisnike da glasuju o vlastitom sadržaju ili izvršiti različite dozvole za različite uloge korisnika.
Ovaj tutorial govori o tome kako to učinitiSupabase authentication and authorizationU ANext.jsAplikacija za.
Mi ćemo početi sSupabase Authza login i upravljanje sjednicama, a zatim dodajteauthorization ruleskorištenjeKontrola pristupa na temelju odnosa (ReBAC)Primjenjivao se krozSupabase Edge Functionsi alocal Policy Decision Point (PDP). u
Na kraju ćete imati aplikaciju za suradnju u stvarnom vremenu koja podržava i javne i zaštićene radnje - i fleksibilan sustav ovlaštenja koji se može razviti kako vaša aplikacija raste.
Što gradimo
U ovom priručniku izgradit ćemo aplikaciju za glasanje u realnom vremenu pomoćuSupabaseiNext.jsTo pokazuje kako autentifikaciju i autorizaciju u akciji.
Aplikacija omogućuje korisnicima da kreiraju ankete, glasuju za druge i upravljaju samo vlastitim sadržajem.Supabase Authza login/signup i kako izvršitiauthorization policiesTko može glasovati, uređivati ili brisati.
Upotrijebit ćemo glavne značajke Supabase-a -Auth,Postgres,RLS,RealtimeiEdge Functions—combined with a Relationship-Based Access Control (ReBAC)Model za provedbu pravila pristupa po korisniku i po resursima.
Tehnologija Stack
- Svijet
- Supabase – Backend-as-a-service za baze podataka, autentifikaciju, funkcije u realnom vremenu i Edge Svijet
- Next.js – Frontend framework za izgradnju aplikacije UI i API putova Svijet
- Permit.io – (za ReBAC) definirati i ocijeniti logiku ovlaštenja putem PDP-a Svijet
- Supabase CLI – za upravljanje i uvođenje Edge funkcija na lokalnoj razini i u proizvodnji Svijet
Preduvjeti
- Svijet
- Node.js je instaliran Svijet
- Baza računa Svijet
- Dozvola.io račun Svijet
- Saznajte više o React/Next.js Svijet
- Početak projekta Repo Svijet
What Can This App Do?
Demo aplikacija je platforma za anketiranje u stvarnom vremenu izgrađena uz Next.js i Supabase, gdje korisnici mogu kreirati ankete i glasati za druge.
- Svijet
- Svaki korisnik (autentificiran ili ne) može vidjeti popis javnih anketa Svijet
- Samo autentični korisnici mogu kreirati ankete i glasati
- Korisnik ne može glasati na anketi koju je stvorio Svijet
- Samo kreator ankete može ga urediti ili izbrisati Svijet
Tutorial Pregled
Slijedit ćemo ove opće korake:
- Svijet
- Uspostavljanje Supabase projekta, sheme, auth i RLS Svijet
- Izgradite osnovne funkcije aplikacije kao što su kreiranje anketa i glasovanje Svijet
- Pravila za autorizaciju modela definiraju uloge i pravila u Permit.io Svijet
- Stvaranje funkcija Supabase Edge za sinhronizaciju korisnika, dodjelu uloga i provjeru dozvola Svijet
- Provedba politika u prednjem dijelu aplikacije pomoću tih funkcija ruba Svijet
Hajde da počnemo -
Setting up Supabase in the Project
Uspostavljanje baze podataka u projektuOpcionalno: Klonirajte predložak Starter
Već sam stvorio aPočetak hramovaOn jeGitHubsa svim kodom koji trebate započeti kako bismo se mogli usredotočiti na provedbu Supabase i Permit.io.
You can clone the project by running the following command:
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
Nakon što ste klonirali projekt, idite u projektni direktorij i instalirajte ovisnosti:
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Creating a new Project in Supabase
Kako započeti:
- Svijet
- Idite na https://supabase.com i prijavite se ili kreirajte račun. Svijet
- Kliknite na "Novi projekt" i unesite ime projekta, lozinku i regiju. Svijet
- Nakon što ste ga stvorili, idite na Project Settings → API i zabilježite svoj Project URL i Anon Key – potrebni su vam kasnije. Svijet
Postavljanje autentifikacije i baze podataka u Supabase-u
Upotrijebit ćemo ugrađenu e-poštu / lozinku Auth:
- Svijet
- U bočnoj traci, idite na Authentication → Providers
- Omogućite pružatelja e-pošte Svijet
- (Opcionalno) Deaktivirajte potvrdu e-pošte za testiranje, ali je držite uključenom za proizvodnju Svijet
Stvaranje sheme baze podataka
Ova aplikacija koristi tri glavne tablice:polls
,options
ivotes
Koristite gaSQL Editoru upravljačkoj ploči Supabase i pokrenite sljedeć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 sigurnosti na razini linije (RLS)
OmogućitiRLSza svaku tablicu i definirati politike:
-- 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()
)
);
Za korištenje funkcija u realnom vremenu baze:
- Svijet
- U bočnoj traci, idite na Urednik stola Svijet
- Za svaku od tri tablice (polls, opcije, glasovi): Kliknite na tri točke → Uredi tablicu Toggle "Omogući u stvarnom vremenu" Sačuvaj promjene Svijet
Implementacija Supabase e-pošte autentifikacije u aplikaciji
U ovoj demo aplikaciji, svatko može pregledati popis anketa dostupnih na aplikaciji, aktivnih i isteklih. Da biste pregledali pojedinosti ankete, upravljali ili glasovali o bilo kojoj anketi, korisnik mora biti prijavljen. Mi ćemo koristiti e-mail i lozinku kao sredstvo autentifikacije za ovaj projekt..env.local
: u
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
Ažurirajte svoju komponentu za prijavu kako biste se bavili prijavom i prijavom putem e-pošte / lozinke:
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;
Ovdje, mi koristimo SupabasesignInWithPassword
način prijavljivanja u korisnika isignUp
način prijavljivanja novog korisnika s njihovom e-poštom i lozinkom. također pohranjujemo ime korisnika uuser_name
polja u korisničkim metapodatcima.
Također možete koristitisupabase.auth.signOut()
kako bi se korisnici prijavili i preusmjerili:
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;
Ovdje koristimosignOut
Metoda iz Supabase za izlazak korisnika i preusmjeriti ih na početnu stranicu.
Slušanje za promjene u stanju autentifikacije korisnika
Slušanje o promjenama u statusu autentifikacije korisnika omogućuje nam ažuriranje korisničkog interfejsa na temelju statusa autentifikacije korisnika.
- Show/hide UI elements like login/logout buttons Svijet
- Uvjetno ograničiti pristup zaštićenim stranicama (kao što je glasovanje ili upravljanje anketama) Svijet
- Ensure only authenticated users can perform restricted actions Svijet
Upotrijebit ćemosupabase.auth.onAuthStateChange()
slušati te događaje i ažurirati aplikaciju u skladu s tim.
In theSvijetLayout.tsx
Svijetfile: 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;
Ograničiti pristup zaštićenim stranicama
On pages like poll detailsilipoll management, također biste trebali slušati promjene stanja autentifikacije kako biste spriječili neovjerene korisnike da im pristupe.
Evo kako to izgleda upages/polls/[id].tsx
: u
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;
And a similar pattern applies in pages/polls/manage.tsx
, gdje korisnici trebaju vidjeti vlastite ankete samo ako su prijavljeni:
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;
Ovi obrasci osiguravaju da vaš UI odražava trenutni status autentifikacije korisnika i predstavlja osnovu za provjere ovlaštenja koje ćemo kasnije dodati.user
Objekt kada se zovecheckPermission
Edge Funkcija za određivanje je li korisniku dopušteno glasovati ili upravljati određenom anketom.
Izgradnja funkcionalnosti aplikacije Polling
With Supabase configured and authentication working, we can now build the core functionality of the polling app. In this section, we’ll cover:
- Stvaranje novih anketa Svijet
- Preuzimanje i prikazivanje anketa u realnom vremenu Svijet
- Uvođenje sustava glasovanja Svijet
This gives us the basic app behavior that we’ll soon protect with fine-grained permissions.
Creating New Polls
Korisnici moraju biti prijavljeni kako bi mogli kreirati ankete.Svako istraživanje uključuje pitanje, datum isteka i skup opcija.Mi također bilježe tko je stvorio istraživanje kako bismo kasnije mogli koristiti taj odnos za kontrolu pristupa.
UnutarNewPoll.tsx
, preuzmite autentificiranog korisnika i upotrijebite Supabase za umetanje ankete i njezinih opcija:
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;
We’ll later call an Edge Function here to assign the “creator” role in Permit.io.
Fetching and Displaying Polls
Polls are divided into active(Još nije istekao) ipastMožete ih preuzeti pomoću upita Supabase filtriranih trenutnim vremenskim žigom i postaviti pretplate u realnom vremenu kako biste odmah odražavali promjene.
Primjer izpages/index.tsx
: u
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 (
...
);
}
Pretraživanje i upravljanje korisničkim anketama
Ovdje prikupljamo aktivne i prethodne ankete izpolls
Također postavljamo pretplatu u realnom vremenu kako bismo slušali promjene upolls
Da bismo razlikovali između aktivnih i prošlih anketa, uspoređujemo datum isteka svakog anketa s trenutnim datumom.
ažuriranje napages/manage.tsx
stranica za preuzimanje i prikazivanje samo anketa koje je stvorio korisnik:
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;
Here, we only fetch polls created by the user and listen for real-time updates in the polls
tablicu tako da je UI ažuriran s najnovijim podacima o anketama.
Also, update the PollCard
komponenta tako da ako je prijavljeni korisnik kreator ankete, ikone za uređivanje i brisanje ankete će im se prikazati na anketi.
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;
Dakle, sada, na kartici ankete, ako je prijavljeni korisnik kreator ankete, prikazat će im se ikone za uređivanje i brisanje ankete.
Uvođenje sustava glasovanja
Logika glasovanja primjenjuje:
- Svijet
- Samo jedan glas po korisniku po anketi
- Kreatori ne mogu glasati na vlastitim anketama Svijet
- Glasovi se pohranjuju u tablicu za glasovanje Svijet
- Rezultati se prikazuju i ažuriraju u realnom vremenu Svijet
Let’s break down how this works in the ViewPoll.tsx
komponentima :
Fetch the Logged-In UserPotreban nam je ID trenutačnog korisnika kako bismo odredili prihvatljivost za glasovanje i zabilježili njihov glas.
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 StatusNakon što imamo korisnika, prikupljamo:
- Sam izbor (uključujući opcije i brojanje glasova)
- Da li je ovaj korisnik već glasao Svijet
Također ćemo ih nazvati kasnije u ažuriranjima u realnom vremenu.
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
Prihvaćamo promjene uvotes
Kada se glasa novo, prikupljamo ažurirane podatke o anketama i status glasa.
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
Ako korisnik nije glasao i dopušteno je glasovati (dodat ćemo provjeru dopuštenja kasnije), umetnemo njegov glas.
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 ResultsIzračunamo ukupni broj glasova i odbrojavanje do vremena isteka, a zatim ga možete koristiti za prikaz postupka ili statistike.
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;
With this setup in place, your voting system is fully functional. But right now, anyone logged in could technically try to vote—even on their own poll. Next, we’ll add authorization checkskorištenjePermit.io and Supabase Edge Functionsza provođenje tih pravila.
Prije nego što to učinimo, prvo pogledajmo vrstu sloja ovlaštenja koji ćemo implementirati.
ReBAC (Relationship-Based Access Control) – kontrola pristupa na temelju odnosa
Supabase handles authentication and basic row-level permissions well, but it doesn’t support complex rules like:
- Svijet
- Kako spriječiti korisnike da glasaju na vlastitim anketama
- Assigning per-resource roles (like “creator” for a specific poll)
- Managing access via external policies Svijet
Da bismo podržali ove vrste dozvola zasnovanih na odnosima, implementirat ćemo ReBAC s Permit.io.
Relationship-Based Access Control (ReBAC)Umjesto da se oslanja isključivo na uloge ili atribute (kao u RBAC-u ili ABAC-u), ReBAC određuje pristup procjenjujući kako je korisnik povezan s resursom koji pokušavaju pristupiti.
Kontrola pristupa na temelju odnosa (ReBAC)
In this tutorial, we apply ReBAC to a polling app:
- Svijet
- Korisnik koji je stvorio anketu je jedini koji može upravljati (uredi / izbriši) Svijet
- Korisnik ne može glasati na vlastitoj anketi
- Other authenticated users can vote once per poll
Modeliranjem tih odnosa u Permit.io-u možemo definirati pravila pristupa koja nadilaze ugrađenu sigurnost na razini linije (RLS) tvrtke Supabase.
Za više informacija o ReBAC-u provjeritePermit.io’s ReBAC dokazi. u
Dizajn kontrole pristupa
For our Polling app, we will define:
- Svijet
- Jedan resurs s akcijama specifičnim za resurse: ankete: stvaranje, čitanje, brisanje, ažuriranje. Svijet
- Two roles for granting permission levels based on a user’s relationship with the resources:
- authenticated: Can perform
create
andread
actions in polls. Can notdelete
, orupdate
actions in polls. - creator: Can
create
,read
,delete
, andupdate
actions in polls. Can performread
andcreate
actions in votes. Cannot usecreate
on their own polls.
Svijet - authenticated: Can perform
Preuzimanje dozvola.io
Let’s walk through setting up the authorization model in Permit.
- Svijet
- Stvoriti novi projekt u Permit.io Nazovite ga nešto poput supabase-polling
- Definicija resursa za ankete Idite na karticu Politika → resursi Kliknite na "Stvoriti resurs" Nazovite to ankete i dodajte akcije: čitati, stvoriti, ažurirati, izbrisati Svijet
- Omogućiti ReBAC za resurs U odjeljku "ReBAC opcije" definirajte sljedeće uloge: autentificirani kreator Kliknite Save
Preuzmite uloge koje smo upravo stvorili.Upozorite da je dopuštenje stvorilo podrazumijevane uloge (admin
,editor
, user
To je nepotrebno za ovaj tutorial.
- Svijet
-
Define access policies
- Go to the Policy → Policies tab
- Use the visual matrix to define:
-
authenticated
canread
andcreate
polls -
creator
canread
,update
, anddelete
polls -
(Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately
-
Svijet - Dodaj instance resursa Idi u Direktorij → Instance Dodaj pojedinačne ID-ove anketa kao instance resursa (to ćete automatizirati kasnije kada se kreiraju nove ankete) Dodijelite uloge korisnicima po anketi (npr. user123 je kreator ankete456) Svijet
This structure gives us the power to write flexible access rules and enforce them per user, per poll.
Now that we have completed the initial setup on the Permit dashboard, let's use it in our application. Next, we’ll connect dopuštenjena naš projekt Supabase putem Edge Functions koji:
- Svijet
- Sinkronizirati nove korisnike Svijet
- Dodjeljivanje uloga kreatora Svijet
- Provjerite pristup na zahtjev Svijet
Setting up Permit in the Polling Application
Dozvola nudi nekoliko načina za integraciju s vašom aplikacijom, ali mi ćemo koristiti Container PDP za ovaj tutorial. Morate gostovati kontejner online da biste ga pristupili u funkcijama Supabase Edge.Željeznica.com. Once you have hosted it, save the url for your container.
Pronađite ključ API-ja za dopuštenje klikom na "Projekti" u bočnoj traci nadzorne ploče za dopuštenje, navigacijom prema projektu na kojem radite, klikom na tri točke i odabirom "Kopiraj ključ API-ja".
Creating Supabase Edge Function APIs for Authorization
Supabase Edge FunctionsOni su savršeni za integraciju usluga trećih strana kao što je Permit.io. Mi ćemo ih koristiti za provedbu naših pravila ReBAC-a u tijeku provjeravanjem je li korisnicima dopušteno obavljati određene radnje na anketama.
Create Functions in Supabase
Inicijalizirajte Supabase u projektu i kreirajte tri različite funkcije pomoćusupabase functions new
Ovo će biti početna točka za vaše funkcije:
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
To će stvoriti afunctions
Brošura usupabase
Sada, hajde da napišemo kode za svaku krajnju točku.
Sljedeći Članak Sljedeći Članak Sinhroniziranje korisnika na Permit.io na Signup (syncUser.ts
) i
Ova funkcija sluša za Supabase'sSIGNED_UP
auth event. When a new user signs up, we sync their identity to Permit.io and assign them the default authenticated
Uloga je.
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" } },
);
}
});
Prikazivanje uloge Stvoritelja (updateCreatorRole.ts
) i
Nakon što korisnik stvori anketu, ova se funkcija poziva na:
- Svijet
- Sinhronizirajte anketu kao novu instanci resursa 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" } }, ); } });
Svijet
Sljedeći Članak Pregled dopuštenja (checkPermission.ts
) i
Ova funkcija djeluje kao čuvar vrata – provjerava je li korisniku dopušteno izvršiti određenu akciju (create
,read
, update
,delete
Na posebnoj anketi.
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" } }
);
}
});
Local Testing
Počnite svoj Supabase dev server kako biste testirali funkcije lokalno:
npx supabase start
npx supabase functions serve
Tada možete dodirnuti svoje funkcije na:
<http://localhost:54321/functions/v1/><function-name>
Primjer :
<http://localhost:54321/functions/v1/checkPermission>
Integracija provjera ovlaštenja u UI
Now that we’ve created our authorization logic with Permit.io and exposed it via Supabase Edge Functions, it’s time to enforce those checks inside the app’s components.
U ovom odjeljku ažurirat ćemo ključne komponente korisničkog interfejsa kako bismo pozvali te funkcije i uvjetno omogućili ili blokirali korisničke akcije kao što su glasovanje ili upravljanje anketama na temelju provjera dozvola.
NewPoll.tsx
Dodijeliti ulogu kreatora nakon kreiranja ankete
Sljedeći članakPoll.tsx
Nakon što kreiramo anketu i sačuvamo je u Supabase, zovemoupdateCreatorRole
Funkcija je:
- Svijet
- Sinhronizirajte novu anketu kao resurs u Permit.io Svijet
- Korisnik koji je stvorio funkciju za postavljanje postavke za trenutni korisnik je stvorio funkciju za postavljanje postavke za trenutni korisnik (Error.Trim() &&options.filter(opt => opt.trim()).Length < 2) { setErrorMessage("ExpiryDate).to ISOString("Please provide a question and at least two options."); return; try { // Create the error const: user_metadata?.Anuser_name, await error: pollError() &&options.filter(opt => opt.trim()) .insert{ question, expires_at: Date new(ExpiryDate).to ISOString), created_by user_error_role(Error_name, error_response: user_metadata?.Anuser_ Svijet
ViewPoll.tsx
Ograničenje glasovanja na temelju dozvola
Prije nego što dopustimo korisniku da glasa na anketi, zovemocheckPermission
funkcije kako bi se provjerilo da imajucreate
Dopuštenje zavotes
Evo kako se primjenjuje pravilo:“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
: Control Access to Edit/Delete
Također ograničavamo radnje upravljanja anketama (uređivanje i brisanje) provjeravanjem ima li korisnikupdate
ilidelete
Odobrenje za tu anketu.
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:
Zamjena za:
{user?.id === poll?.created_by && (
sa :
{canManagePoll && (
<div className="flex justify-start gap-4 mt-4">
<button type="button" onClick={handleEdit}>
</button>
<button type="button" onClick={handleDelete}>
</button>
</div>
)}
Testiranje integracije
Nakon integracije, trebali biste vidjeti sljedeće ponašanje u aplikaciji:
- Svijet
- Isključeni korisnici mogu vidjeti ankete, ali ne i komunicirati Svijet
- Autentificirani korisnici mogu glasati na anketama koje nisu stvorili Svijet
- Kreatori ne mogu glasati na vlastitim anketama Svijet
- Samo kreatori vide opcije za uređivanje / brisanje na svojim anketama Svijet
You should be able to see the application's changes by going to the browser. On the home screen, users can view the list of active and past polls, whether they are logged in or not. However, when they click on a poll, they will not be able to view the poll details or vote on it. Instead, they will be prompted to log in.
Once logged in, the user can view the details of the poll and vote on it. However, if the user is the creator of the poll, they will not be able to vote on it. They will see a message indicating that they cannot vote on their own poll. They will also be allowed to manage any poll that they create.
Zaključak
In this tutorial, we explored how to implement Supabase authentication and authorizationU stvarnom svijetuNext.jsAplikacija za.
Počeli smo s postavljanjemSupabase Authza prijavu i prijavu, stvorio relacijsku shemu s sigurnosnim sustavom razine rublja i dodao dinamičku logiku ovlaštenja pomoćuReBACUz pomoćSupabase Edge Functionsi aPolicy Decision Point (PDP), izvršili smo provjere dozvola izravno s frontenda.
KombinirajućiSupabase AuthS fleksibilnom kontrolom pristupa mogli smo:
- Svijet
- Autentificiranje korisnika putem e-pošte i lozinke Svijet
- Ograničiti glasovanje i upravljanje anketama na ovlaštene korisnike Svijet
- Sljedeći Članak Sprječavanje kreatora da glasaju na vlastitim anketama Svijet
- Dodjeljivanje i ocjenjivanje uloga korisnika na temelju odnosa s podacima Svijet
Ova postavka pruža vam skalabilnu osnovu za izgradnju aplikacija koje zahtijevaju autentifikaciju i autorizaciju s finim zrnom.
daljnje čitanje
- Svijet
- Permit.io ReBAC vodič Svijet
- Dozvola + pružatelji autentikacije Svijet
- Elementi dopuštenja: ugrađeni UI za upravljanje ulogama Svijet
- Filtriranje podataka s dopuštenjem Svijet
- Revizorski logovi Svijet
Imate pitanja? pridružite nam seSlack zajednica, gdje stotine programera grade i raspravljaju o autorizaciji.
Slack zajednica