deurGabriel L. Manor
Supabase maak dit maklik om outentieking by jou app te voeg met ingeboude ondersteuning vir e-pos, OAuth en magiese skakels.Maar terwyl Supabase Auth hanteer wie jou gebruikers is, benodig jy dikwels ook 'n outentieklaag.
Supabase bied 'n goeie backend met ingeboude auth en Row Level Security (RLS), bestuurfine-grained permissionsVooral diegene wat gebaseer is oprelationships between users and dataDit is ver van maklik.
U wil dalk aksie beperk, soos die redigering of verwydering van data aan hulpbron eienaars, gebruikers te verhoed om te stem op hul eie inhoud, of die handhawing van verskillende toestemmings vir verskillende gebruiker rolle.
Hierdie tutorial gaan deur hoe om te implementeerSupabase authentication and authorizationin die aNext.jsdie toepassing.
Ons sal begin metSupabase Authvir login en sessie bestuur, dan voegauthorization rulesgebruikGebaseerde toegangsbeheer (ReBAC)Verpligting deurSupabase Edge Functionsen alocal Policy Decision Point (PDP)Die
Uiteindelik sal u 'n real-time gesamentlike polling-app hê wat beide openbare en beskermde aksies ondersteun - en 'n buigsaamheidstelsel wat u kan ontwikkel as u app groei.
Wat ons bou
In hierdie gids, sal ons 'n real-time polling app bou met behulp vanSupabaseenNext.jsDit toon beide authenticasie en autorisasie in aksie.
Die app laat gebruikers toe om sonde te skep, oor ander te stem en slegs hul eie inhoud te bestuur.Supabase Authvir login / handtekening en hoe om dit uit te voerauthorization policiesdie beheer wat kan stem, redig, of verwyder.
Ons sal die kernkenmerke van Supabase gebruik—AuthdiePostgresdieRLSdieRealtime, and Edge FunctionsGekombineer met 'nRelationship-Based Access Control (ReBAC)model om per gebruiker en per hulpbron toegangsreëls te handhaaf.
Die Tech Stack
- die
- Supabase – Backend-as-a-service vir databasis, outentiek, werklike tyd en kant funksies die
- Next.js – Frontend framework vir die bou van die app UI en API roetes die
- Permit.io – (vir ReBAC) om autorisasie logika te definieer en te evalueer via PDP die
- Geskryf deur CLI – To manage and deploy Edge Functions locally and in production die
Voorwaardes
- die
- Node.js word geïnstalleer die
- Basiese rekening die
- Toelaat.io rekening die
- Kennis van React/Next.js die
- Die begin van die repo-projek die
Wat kan hierdie app doen?
Die demo-toepassing is 'n real-time polling platform gebou met Next.js en Supabase, waar gebruikers polls kan skep en stem oor ander.
- die
- Elke gebruiker (geauthenteer of nie) kan die lys van openbare sonde sien die
- Slegs geauthentifiseerde gebruikers kan sonde skep en stem die
- 'N Gebruiker kan nie stem op 'n poll wat hulle geskep het nie die
- Slegs die skepper van 'n opname kan dit bewerk of verwyder die
Tutoriale oorsig
Ons sal hierdie algemene stappe volg:
- die
- Stel die Supabase-projek, skema, auth en RLS op die
- Bouw kernapp funksies soos opstel van sonde en stemming die
- Modelle autorisasie reëls definieer rolle en reëls in Permit.io die
- Skep Supabase Edge-funksie vir die sinchronisering van gebruikers, die toekenning van rolle en die kontroleer van toestemmings die
- Uitvoer beleid in die app frontend met behulp van daardie edge funksies die
Kom ons begin -
Setting up Supabase in the Project
Skep 'n Supabase in die projekOptioneel: Klon die Starter Template
Ek het reeds 'nDie tempel begindieGitHubmet al die kode wat jy nodig het om te begin sodat ons kan fokus op die implementering van Supabase en Permit.io.
Jy kan die projek kloneer deur die volgende bevel uit te voer:
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
Sodra jy die projek gekloon het, navigeer na die projek directory en installeer die afhankings:
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Maak 'n nuwe projek in Supabase
Om te begin:
- die
- Gaan na https://supabase.com en log in of skep 'n rekening. die
- Klik op "Nuwe projek" en vul jou projeknaam, wagwoord en streek in. die
- Sodra dit geskep is, gaan na Project-instellings → API en let op jou Project-URL en Anon-sleutel - jy sal hulle later nodig hê. die
Authentiekering en databasis in Supabase
Ons sal Supabase se ingeboude e-pos / wagwoord auth gebruik:
- die
- In die zijbalk, gaan na Authentification → Providers die
- Toelaat die e-pos verskaffer die
- (Optioneel) Deaktiveer bevestigings-e-pos vir toets, maar hou dit geaktiveer vir produksie die
Die skep van die databasis skema
Hierdie app gebruik drie hoof tabelle:polls
dieoptions
envotes
Gebruik dieSQL Editorin die Supabase dashboard en die volgende uit te voer:
-- 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)
);
Bepaal die vlak van veiligheid (RLS)
ToelaatDie RLSvir elke tabel en definieer beleid:
-- 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()
)
);
Om die real-time funksies van Supabase te gebruik:
- die
- In die sidebar, gaan na Table Editor die
- Vir elkeen van die drie tabelle (opnames, opsies, stemme): Klik op die drie punte → Redig Tabel Toggle "Aktiewe Realtime" Bewaar veranderinge die
Implementeer Supabase Email Authentification in die App
In hierdie demo-app kan enige persoon die lys van opnames wat beskikbaar is op die app sien, beide aktief en verval. Om die besonderhede van 'n opname te sien, te bestuur, of te stem op enige opname, moet die gebruiker aangemeld wees. Ons sal e-pos en wagwoord gebruik as 'n manier van outentiek vir hierdie projek..env.local
: die
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
Opdater jou login komponent om beide registrasie en login via e-pos / wagwoord te hanteer:
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;
Hier, ons gebruik Supabase sesignInWithPassword
die metode om te log in 'n gebruiker en diesignUp
methode om 'n nuwe gebruiker te registreer met hul e-pos en wagwoord. Ons sal ook die gebruikersnaam in dieuser_name
die metadata van die gebruiker.
Jy kan ook gebruiksupabase.auth.signOut()
Om gebruikers uit te log en hulle te oorgedra:
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;
Hier gebruik ons diesignOut
methode van Supabase om die gebruiker uit te log en hulle na die tuisblad te oorgedra.
Luister na veranderinge in die gebruiker se authenticatie-toestand
Die luister na veranderinge in die gebruikers-authentifikasie-toestand laat ons toe om die UI op grond van die gebruikers-authentifikasie-toestand te actualiseer.
- Show/hide UI elements like login/logout buttons die
- Voorwaardelike beperking van toegang tot beskermde bladsye (soos stem of bestuur van sonde) die
- Ensure only authenticated users can perform restricted actions
Ons sal gebruiksupabase.auth.onAuthStateChange()
om hierdie gebeure te luister en die app dienlik te actualiseer.
In thedieLayout.tsx
diefile: 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;
Restrict Access on Protected Pages
Op bladsye soospoll details or poll management, moet jy ook luister na oordragstaatsveranderinge om te verhoed dat nie-oordragte gebruikers toegang tot hulle kry.
Hier is hoe dit lyk inpages/polls/[id].tsx
: die
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;
En 'n soortgelyke patroon is van toepassing inpages/polls/manage.tsx
, waar gebruikers net hul eie navorsings moet sien as hulle aangemeld is:
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;
Hierdie patrone maak seker jou UI weerspieël die gebruiker se huidige outentiekstatus en vorm die basis vir die outentiekskoers wat ons later sal byvoeg.user
Die voorwerp wanneer die oproepcheckPermission
Edge Funksie om te bepaal of 'n gebruiker toegelaat word om te stem of 'n spesifieke opname te bestuur.
Opbou van die Polling App-funksionaliteit
Met Supabase gekonfigureer en verifikasie werk, kan ons nou die kernfunksionaliteit van die polling-program bou.
- Creating new polls
- Opname en weergave van opnames in werklike tyd
- Invoering van 'n stemmingstelsel
Dit gee ons die basiese app-gedrag wat ons binnekort sal beskerm met goedgekeurde toestemmings.
Creating New Polls
Gebruikers moet aangemeld wees om opnames te skep. Elke opname bevat 'n vraag, 'n vervaldatum en 'n stel opsies. Ons registreer ook wie die opname geskep het sodat ons later daardie verhouding kan gebruik vir toegangskontrole.
BinneNewPoll.tsx
, kry die geauthentifiseerde gebruiker, en gebruik Supabase om die opname en sy opsies in te voeg:
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;
Ons sal later 'n Edge-funksie hier oproep om die "skepper" -rol in Permit.io toe te pas.
Opname en vertoon van opnames
Polls are divided into active(Nog nie verby is nie) enpast (expired). You can fetch them using Supabase queries filtered by the current timestamp, and set up real-time subscriptions to reflect changes instantly.
Voorbeeld vanpages/index.tsx
: die
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 (
...
);
}
Sien en bestuur gebruikers opnames
Hier kry ons aktiewe en verlede sonde van diepolls
die tabel in Supabase. Ons stel ook 'n real-time-abonnement in om na veranderinge in diepolls
Tabel sodat ons die UI met die nuutste navorsingsdata kan actualiseer.Om tussen aktiewe en vorige navorsings te onderskei, vergelyk ons die vervaldatum van elke navorsing met die huidige datum.
Opdatering van diepages/manage.tsx
bladsy om slegs sonde wat deur die gebruiker geskep is, te vang en weer te stel:
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;
Hier kry ons slegs sonde wat deur die gebruiker geskep is en luister na real-time updates in diepolls
Tabel sodat die UI opgedateer word met die nuutste poll data.
Op die oomblik is die updatePollCard
component so that if a logged-in user is the poll creator, icons for editing and deleting the poll will be displayed to them on the poll.
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;
So nou, op 'n opname kaart, as die ingelogde gebruiker is die opname maker, ikone vir die redigering en verwydering van die opname sal vir hulle weergegee word.
Die implementering van die stemmingstelsel
Die stemmingslogika impliseer:
- Only one vote per user per poll
- Skepters kan nie op hul eie sonde stem nie
- Stemmen word in die stemmingstabel gestoor die
- Resultate word weergegee en opgedateer in werklike tyd die
Kom ons breek af hoe dit werk in dieViewPoll.tsx
component:
Fetch the Logged-In UserWe need the current user’s ID to determine voting eligibility and record their vote.
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 StatusSodra ons die gebruiker het, kry ons:
- die
- Die poll self (insluitend opsies en stemrekening)
- Of hierdie gebruiker reeds gestem het die
Ons noem dit ook later weer in real-time updates.
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
We subscribe to changes in the votes
Wanneer 'n nuwe stemming gestuur word, kry ons geactualiseerde stemming data en stemming status.
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
As die gebruiker nie gestem het nie en toegelaat word om te stem (ons sal later 'n toestemmingskoers byvoeg), voeg ons sy stem in.
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 ResultsOns bereken die totale aantal stemme en 'n terugrekening na die verval tyd.
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;
Met hierdie instelling in plek, jou stemmingstelsel is ten volle funksioneel. Maar op die oomblik, almal wat aangemeld is, kan tegnies probeer om te stem - selfs op hul eie stemming.authorization checksgebruikPermit.ioenSupabase Edge Functionsom hierdie reëls te handhaaf.
Voordat ons dit doen, laat ons eers kyk na die tipe autorisasielaag wat ons gaan implementeer.
Understanding ReBAC (Relationship-Based Access Control)
Supabase hanteer goed verifikasie en basiese limiete op rigtingsvlak, maar dit ondersteun nie komplekse reëls soos:
- die
- Om gebruikers te verhoed om op hul eie sonde te stem die
- Toewysing van rolle per hulpbron (soos "skepper" vir 'n spesifieke opname)
- Managing access via external policies die
Om hierdie soort verhouding gebaseerde toestemmings te ondersteun, implementeer ons ReBAC met Permit.io.
Relationship-Based Access Control (ReBAC)is 'n model vir die bestuur van toestemmings wat gebaseer is op die verhoudings tussen gebruikers en hulpbronne. In plaas van uitsluitlik te vertrou op rolle of attribute (soos in RBAC of ABAC), bepaal ReBAC toegang deur te evalueer hoe 'n gebruiker verbind is met die hulpbron wat hulle probeer om toegang te verkry.
Gebaseerde toegangsbeheer (ReBAC)
In hierdie tutorial, ons toepas ReBAC op 'n stemming app:
- die
- A user who created a poll is the only one who can manage (edit/delete) it die
- 'N Gebruiker kan nie op sy eie poll stem nie die
- Other authenticated users can vote once per poll die
Deur hierdie verhoudings in Permit.io te model, kan ons goedgekeurde toegangsreëls definieer wat verder gaan as Supabase se ingeboude Row Level Security (RLS).
For more on ReBAC, check out Permit.io se ReBAC dokumenteDie
Ontwerp van toegangsbeheer
Vir ons Polling app, sal ons definieer:
- die
- Een hulpbron met hulpbron-spesifieke aksies: sonde: skep, lees, verwyder, actualiseer. die
- Twee rolle vir toekenning van toestemmingsvlakke gebaseer op 'n gebruiker se verhouding met die hulpbronne: geauthentifiseer: Kan maak en lees aksies in opnames. Kan nie verwyder of opdater aksies in opnames. maker: Kan skep, lees, verwyder en opdater aksies in opnames. Kan lees en skep aksies in stemme. Kan nie gebruik maak op hul eie opnames.
Maak die toestemming.io
Let’s walk through setting up the authorization model in Permit.
- die
- Maak 'n nuwe projek in Permit.io Naam dit iets soos supabase-polling die
- Definieer die sonde hulpbron Gaan na die Beleid → hulpbronne tab Klik op “Skep hulpbron” Naam dit sonde, en voeg die aksies by: lees, skep, actualiseer, verwyder die
- Om ReBAC vir die hulpbron te aktiveer Onder "ReBAC-opsies," definieer die volgende rolle: geauthenticeerde skepper Klik Save die
Navigeer na die "Roles" tab om die rolle te sien van die hulpbronne wat ons net geskep het. Let daarop dat Toestemming die standaard rolle geskep het (admin
, editor
, user
Dit is onnodig vir hierdie tutorial.
- Definieer toegangsbeleid Gaan na die Beleid → Beleid tab Gebruik die visuele matrix om te definieer: geauthentifiseerde kan polls lees en skep die skepper kan polls lees, opdat en verwyder (Optioneel) U kan stemreëls as deel hiervan of via 'n tweede hulpbron instel as u afsonderlik stemmodelleer.
-
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
)
die -
Hierdie struktuur gee ons die mag om fleksibele toegangsreëls te skryf en hulle per gebruiker, per opname, uit te voer.
Now that we have completed the initial setup on the Permit dashboard, let's use it in our application. Next, we’ll connect Toelaatna ons Supabase-projek via Edge Funksie wat:
- die
- Sync nuwe gebruikers die
- Toekenning van kreatiewe rolle
- Kontroleer toegang op versoek
Setting up Permit in the Polling Application
Permit bied verskeie maniere om te integreer met jou aansoek, maar ons sal die Container PDP gebruik vir hierdie gids. Jy moet die container aanlyn hosting om dit in Supabase Edge funksies te toegang.die spoorweg.com. Once you have hosted it, save the url for your container.
Kry jou toestemming API sleutel deur te klik op "Projekte" in die toestemming instrumentbalk, navigeer na die projek waarvoor jy werk, klik op die drie punte en kies "Kopieer API sleutel".
Skep Supabase Edge Function API's vir Autorisasie
Supabase Edge Functions are perfect for integrating third-party services like Permit.io. We’ll use them to enforce our ReBAC rules at runtime by checking whether users are allowed to perform specific actions on polls.
Create Functions in Supabase
Initialiseer Supabase in jou projek en skep drie verskillende funksies met behulp van diesupabase functions new
command. These will be the starting point for your functions:
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
Dit sal 'nfunctions
Die folder in diesupabase
Kom ons skryf die kode vir elke eindpunt.
Die versekering van die versekering van die versekering van die versekering (syncUser.ts
) die
Hierdie funksie luister vir Supabase'sSIGNED_UP
Wanneer 'n nuwe gebruiker aanmeld, sinchroniseer ons hul identiteit met Permit.io en gee hulle die standaardauthenticated
role.
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" } },
);
}
});
Verwys na die rol van die skepper (updateCreatorRole.ts
) die
Sodra 'n gebruiker 'n opname skep, word hierdie funksie geroep om:
- die
- Synchroniseer die opname as 'n nuwe Permit.io hulpbron-instansie die
- As gevolg van hierdie resource: Resource geassosieer die gebruiker die rol van die skepper vir die opname status "jsr:@supabase/functions-js/edge-runtime.d.ts"; gebruiker {Permit } van "npm:permitio"; resource new return corsHeaders = { 'Access-Control-message-Allow-Origin': "*", 'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type', 'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE', 'Error(req) => 'Access-serve(async)', assosieer die rol van die gebruiker: {Idstr die
Toelaatbare toelaatings (checkPermission.ts
) die
Hierdie funksie optree as die gatekeeper - dit kyk of 'n gebruiker toegelaat word om 'n gegewe aksie uit te voer (create
dieread
dieupdate
diedelete
In 'n spesifieke 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, 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
Start your Supabase dev server to test the functions locally:
npx supabase start
npx supabase functions serve
Jy kan dan jou funksies op:
<http://localhost:54321/functions/v1/><function-name>
Voorbeeld van:
<http://localhost:54321/functions/v1/checkPermission>
Integrasie van autorisasiekontrole in die UI
Nou dat ons ons autorisasie-logika met Permit.io geskep het en dit via Supabase Edge Functions blootgestel het, is dit tyd om daardie kontrole binne die komponente van die app te handhaaf.
In hierdie afdeling sal ons belangrike UI-komponente opdater om daardie funksies te bel en voorwaardelik gebruikersaktiwiteite te toelaat of te blokkeer, soos stemming of die bestuur van sonde gebaseer op toestemmingskontrole.
NewPoll.tsx
: Toekenning van die skeprol na die skep van die opname
Sien die.tsx
After creating a poll and saving it to Supabase, we call the updateCreatorRole
Funksie vir:
- die
- Sinkroniseer die nuwe opname as 'n hulpbron in Permit.io die
-
Assign the current user the
creator
role for that specific poll
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; } try { // 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) { console.error("Error creating options:", optionsError); setErrorMessage(optionsError.message); return; } // Update the creator role in Permit.io const response = await fetch( "<http://127.0.0.1:54321/functions/v1//updateCreatorRole>", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ userId: user?.id, pollId: poll.id, }), } ); const { success, error } = await response.json(); if (!success) { console.error("Error updating creator role:", error); // Note: We don't set an error message here as the poll was still created successfully } setSuccessMessage("Poll created successfully!"); handleCancel(); } catch (error) { console.error("Error in poll creation process:", error); setErrorMessage("An unexpected error occurred while creating the poll."); } };
die
ViewPoll.tsx
: Beperkte stemming op grond van toestemmings
Before allowing a user to vote on a poll, we call the checkPermission
Funksie om te verifieer dat hulle diecreate
Toestemming vir dievotes
resource.Dit is hoe ons die reël handhaaf:“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
• Toegang tot die opsie Edit/Delete
Ons beperk ook opname bestuur aksies (redig en verwyder) deur te kyk of die gebruiker dieupdate
ofdelete
Toestemming vir die poll.
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:
vervanging van:
{user?.id === poll?.created_by && (
Met die:
{canManagePoll && (
<div className="flex justify-start gap-4 mt-4">
<button type="button" onClick={handleEdit}>
</button>
<button type="button" onClick={handleDelete}>
</button>
</div>
)}
Testeer die integrasie
Sodra dit geïntegreer is, moet jy die volgende gedrag in die app sien:
- die
- Gelaagde gebruikers kan navorsings sien, maar nie interaksie maak nie die
- Authentifiseerde gebruikers kan stem op sonde wat hulle nie geskep het nie
-
Creators cannot vote on their own polls
die - Slegs skepers sien bewerk/verwyder opsies op hul sonde die
Jy moet in staat wees om die veranderinge van die aansoek te sien deur na die leser te gaan. Op die aanvanklike skerm kan gebruikers die lys van aktiewe en verlede sonde sien, of hulle aangemeld is of nie.
Sodra aangemeld is, kan die gebruiker die besonderhede van die opname sien en daarop stem. As die gebruiker egter die skepper van die opname is, sal hulle nie daarop kan stem nie. Hulle sal 'n boodskap sien wat aangedui dat hulle nie op hul eie opname kan stem nie.
Konklusie
In hierdie tutorial het ons ondersoek hoe om te implementeerSupabase authentication and authorizationIn 'n werklike wêreldNext.jsdie toepassing.
Ons het begin met die opstelSupabase Authvir inlog en aanmelding, 'n relasionele skema met Row Level Security geskep, en voeg dinamiese autorisasie logika gebruikReBACMet die hulp vanSupabase Edge Functionsen aPolicy Decision Point (PDP), ons toegepas toestemmings kontrole direk van die frontend.
deur die kombinasieSupabase AuthMet 'n fleksibele toegangsbeheer kon ons:
- die
- Authentiek gebruikers via e-pos en wagwoord die
- Beperk stem- en pollbestuur tot gemagtigde gebruikers die
- Beperk die skepers om te stem op hul eie sonde die
- Toewys en evalueer gebruiker rolle gebaseer op verhoudings met data die
Hierdie instelling gee jou 'n skaalbare basis vir die bou van programme wat beide outentiek en goedgekeurde autorisasie benodig.
Verdere lees
- die
- Permit.io ReBAC gids die
- die
- Toelaatingselemente: ingebed UI vir rolbestuur die
- die
- Audit logs die
Het jy vrae? sluit aan by onsSlack gemeenskap, waar honderde ontwikkelaars bou en bespreek autorisasie.
Slack gemeenskap