Lisolo ya sika

Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela

pene Permit.io40m2025/04/16
Read on Terminal Reader

Molai mingi; Mpo na kotánga

Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba la Pamba.
featured image - Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela Kofutela
Permit.io HackerNoon profile picture
0-item


Bisali Bisali

Mongolo-Mongolo


Supabase na lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa lisa


Supabase na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na nafine-grained permissionsmisi na mtumiaji na data


You may want to restrict actions like editing or deleting data to resource owners, prevent users from voting on their own content, or enforce different permissions for different user roles.


This tutorial walks through how to implement Supabase authentication and authorization in a Next.js application.

Supabase authentication and authorizationNext.js

We'll start with Supabase Auth for login and session management, then add authorization rules using Relationship-Based Access Control (ReBAC), enforced through Supabase Edge Functions and a local Policy Decision Point (PDP).

Supabase AuthmisoRelationship-Based Access Control (ReBAC)Supabase Edge Functionslocal Policy Decision Point (PDP)


By the end, you’ll have a real-time collaborative polling app that supports both public and protected actions—and a flexible authorization system you can evolve as your app grows.

What We’re Building

Nde ya motuna, s'azinga app ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna yaSupabaseNext.js


Let's app na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na naSupabase Authmiso


Metaboliki ya Supabase->Auth, Postgres, RLS, Realtime, na Edge Functions-yaki na Relationship-Based Access Control (ReBAC) modela ekomisa miso ya miso ya miso ya miso ya miso ya miso ya miso.

TangoPostgresRLSNtanetiEdge FunctionsRelationship-Based Access Control (ReBAC)

Tech Stack

  • Supabase – Backend-as-a-service for database, authentication, realtime, and edge functions
  • Next.js – Frontend framework for building the app UI and API routes
  • Permit.io – (for ReBAC) to define and evaluate authorization logic via PDP
  • Supabase CLI – To manage and deploy Edge Functions locally and in production
  • Supabase – Backend-as-a-service for database, authentication, realtime, and edge functions
  • SupabaseSupabase
  • Next.js – Frontend framework for building the app UI and API routes
  • Next.jsNext.js
  • Permit.io – (for ReBAC) to define and evaluate authorization logic via PDP
  • Permit.ioPermit.io
  • Supabase CLI – To manage and deploy Edge Functions locally and in production
  • Supabase CLISupabase CLI

    Bisáleli

  • Node.js installed
  • Supabase account
  • Permit.io account
  • Permit.io
  • Ntaneti na React/Next.js
  • Starter project repo
  • Starter project repo

    What Can This App Do?

    The demo application is a real-time polling platform built with Next.js and Supabase, where users can create polls and vote on others.


    • Any user (authenticated or not) can view the list of public polls
    • Only authenticated users can create polls and vote
    • A user cannot vote on a poll they created
    • Only the creator of a poll can edit or delete it
  • Any user (authenticated or not) can view the list of public polls
  • Only authenticated users can create polls and vote
  • A user cannot vote on a poll they created
  • bomba ba ndingisa ba ndingisa na ndingisa na ndingisa na ndingisa na ndingisa na ndingisa
  • Only the creator of a poll can edit or delete it
  • pe-po-girl14

    Tutorial Tango

    We’ll follow these general steps:


    1. Set up Supabase project, schema, auth, and RLS
    2. Build core app features like poll creation and voting
    3. Model authorization rules define roles and rules in Permit.io
    4. Create Supabase Edge Functions for syncing users, assigning roles, and checking permissions
    5. Enforce policies in the app frontend using those edge functions
  • Ndiza Supabase project, schema, auth, na RLS
  • Ntlwisi mbongo
  • Ndiza miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya misoBuild core app miso
  • Ntloko ya modela ekomisa miso na miso na Permit.io
  • Model authorization rules
  • Moke Supabase Edge Functions ya simoloka watsa na miso na miso na miso na miso na miso na miso
  • Create Supabase Edge Functions
  • Ndima ya miso ya miso ya miso ya miso ya miso
  • Enforce policies


    Let's get started -

    Setting up Supabase in the Project

    Setting up Supabase in the Project

    Optional: Clone the Starter Template

    I've already created a starter template on GitHub with all the code you need to start so we can focus on implementing Supabase and Permit.io.

    starter templateKitHub


    You can clone the project by running the following command:


    Eko bebisa ba robots nyonso, mpe o komona esengo na Internet !git clone <https://github.com/permitio/supabase-fine-grained-authorization>


    Komi ya boteyi, boteyi na boteyi na boteyi na boteyi na boteyi na boteyi:


    cd realtime-polling-app-nextjs-supabase-permitio
    npm install
    
    cd realtime-polling-app-nextjs-supabase-permitio npm install

    Creating a new Project in Supabase

    To get started:

    • Go to https://supabase.com and sign in or create an account.

    • Click "New Project" and fill in your project name, password, and region.

    • Once created, go to Project Settings → API and note your Project URL and Anon Key — you’ll need them later.


  • Go to https://supabase.com and sign in or create an account.

  • Go to https://supabase.com and sign in or create an account.

    https://supabase.com
  • Click "New Project" and fill in your project name, password, and region.

  • Click "New Project" and fill in your project name, password, and region.

    "Motobaz"
  • Once created, go to Project Settings → API and note your Project URL and Anon Key — you’ll need them later.


  • Once created, go to Project Settings → API and note your Project URL and Anon Key — you’ll need them later.

    Project Settings → APIProject URLAnon Key


    Nkinga Authentication na Database na Supabase

    We’ll use Supabase’s built-in email/password auth:

    • In the sidebar, go to Authentication → Providers

    • Enable the Email provider

    • (Optional) Disable email confirmation for testing, but keep it enabled for production


  • Na boti na boti na boti na boti na boti na boti na boti na boti

    Ndi na boti na boti na boti na boti na boti na boti na boti na boti na botiAuthentication → Providers

  • Ndimi na Email na yo

  • Enable the Email provider

    Email
  • (Optional) Kofutela e-mail ya konfirmation ya motuna, mpe o komona ya motuna ya motuna


  • (Optional) Kofutela e-mail ya konfirmation ya motuna, mpe o komona ya motuna ya motuna ya motuna


    Creating the Database Schema

    This app uses three main tables: polls, options, and votes. Use the SQL Editor in the Supabase dashboard and run the following:

    pollsoptionsvotesSQL Editor


    -- 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)
    );
    
    -- 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) );

    Enabling Row Level Security (RLS)

    Enable RLS for each table and define policies:

    RLS
    -- Polls politics ALTER TABLE polls ENABLE ROW LEVEL SECURITY; CREATE POLICY "Anyone can view polls" ON polls FOR SELECT USING (true); CREATE POLICY "Anyone can view options" ON options FOR SELECT USING (true); CREATE POLICY "Poll creators can add options" ON OPTIONS FOR INSERT TO AUTHENTIZED WITH CHECK (auth.uid() = created_by); -- Options POLICY ALTER TABLE options ENABLE ROW LEVEL SECURITY (EXISTS (SELECT 1 FROM polls WHERE votes idERE = options.pollid_ECECT AND created auth_by auth.-- 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()
            )
        );
    


    To use Supabase’s real-time features:

    • In the sidebar, go to Table Editor


    • For each of the three tables (polls, options, votes):

      • Click the three dots → Edit Table

      • Toggle "Enable Realtime"

      • Save changes


  • In the sidebar, go to Table Editor


  • In the sidebar, go to Table Editor

    Table Editor


  • For each of the three tables (polls, options, votes):

    • Click the three dots → Edit Table

    • Toggle "Enable Realtime"

    • Save changes


  • For each of the three tables (polls, options, votes):

    pollsoptionsvotes
    • Click the three dots → Edit Table

    • Toggle "Enable Realtime"

    • Save changes


  • Kisa ba pound → Edit Table

  • Click the three dots → Edit Table

    Edit Table
  • Toggle "Enable Realtime"

  • Toggle "Ndimi"

    "Enable Realtime"
  • Save changes


  • Save changes


    Implementing Supabase Email Authentication in the App

    Nde ya demo app, na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na.env.local

    NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key 
    NEXT_PUBLIC_SUPABASE_URL=your_supabase_url NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key


    Ndimi ya nongo ya nongo ya nongo ya nongo ya nongo na nongo ya nongo ya nongo ya nongo ya nongo ya nongo ya nongo ya nongo ya nongo :


    import { useState } daga "react"; import { createClient } daga "@/utils/supabase/komponent"; const LogInButton = () => { const supabase = createClient(); async function logIn() { const { error } = await supabase.auth.signUp email({ data: user_name: userName, }, }); if (error) { setError(error.message); } else {ShowModal(false); } } async function signUp() { const error } { const { } = await supabase.auth.signUp email({ error.auth.sign}, default email, { data: user_name, password, }; } (error)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;
    


    Here, we are using Supabase’s signInWithPassword method to log in a user and the signUp method to sign up a new user with their email and password. We are also storing the user's name in the user_name field in the user's metadata.

    signInWithPasswordsignUpuser_name


    Bambisani mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbongo mbsupabase.auth.signOut()


    imporite { createClient } daga "@/utils/supabase/component"; import {useRouter } daga "next/router"; const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => { const router = useRouter(); const supabase = createClient(); const handleLogOut = async () => { await supabase.auth.signOut(); closeDropdown(); router.push("/"}); back ( ... ); } export default LogOutButton; 
    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;


    Here, we are using the signOut method from Supabase to log out the user and redirect them to the home page.

    signOut

    Misa ya motuna ya motuna ya motuna ya motuna

    Nkinga miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso.


    • Show/hide UI elements like login/logout buttons
    • Conditionally restrict access to protected pages (like voting or managing polls)
    • Ensure only authenticated users can perform restricted actions
  • Show/hide elementi UI kama login/logout buttons
  • Komi ya ba motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna ya motuna
  • Ensure only authenticated users can perform restricted actions

  • Na na na na na na na na na na na na na na na na na na na na na na na na na na na na na na nasupabase.auth.onAuthStateChange()


    In the Layout.tsx file: Track Global Auth State

    In theLayout.tsxfile: Track Global Auth State


    import React, { usEffect, useState } daga "react"; import { createClient } daga "@/utils/supabase/component"; import { User } daga "@supabase/supabase-js"; const Layout = ({ watoto }: { watoto: React.ReactNode( )); { data = supabase.authon.AuthonStateChange((event, session) => usEffect(() => { const fetchUser = async () => { const supabase = createClient(); { data = supabase.authon.AuthonStateChange((event, session))) => { set(Useression?.user.Pitch null); }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

    Kitamba mbongo mbongo na mbongo mbongo, pe-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-popoll detailspoll management


    Here’s how it looks in pages/polls/[id].tsx:

    pages/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;
    
    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, where users should only see their own polls if logged in:

    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;
    
    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;


    Ndiyo miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso. Miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso.

    usercheckPermission

    Ntlalo ya Polling App

    With Supabase configured and authentication working, we can now build the core functionality of the polling app. In this section, we’ll cover:


    • Creating new polls
    • Fetching and displaying polls in real-time
    • Implementing a voting system
  • Creating new polls
  • Fetching and displaying polls in real-time
  • Moke ya motuna

  • Eto na na na na na na na na na na na na na na na na na na na na na na na na na

    Creating New Polls

    Users must be logged in to create polls. Each poll includes a question, an expiration date, and a set of options. We also record who created the poll so we can later use that relationship for access control.


    Inside NewPoll.tsx, fetch the authenticated user, and use Supabase to insert the poll and its options:

    NewPoll.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;
    
    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 (not yet expired) and past (expired). You can fetch them using Supabase queries filtered by the current timestamp, and set up real-time subscriptions to reflect changes instantly.

    activemoko


    Bomba ya pages/index.tsx:

    pages/index.tsx


    ETENI YA 10 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YA 12 - ETENI YAimport { 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 table in Supabase. We are also setting up a real-time subscription to listen for changes in the 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.

    pollspolls


    Update the pages/manage.tsx page to fetch and display only polls created by the user:

    pages/manage.tsx


    imporite { PollProps } daga "@/polls"; const Page = () => { useEffect(() => { if (!user?.id) return; const fetchPolls = async () => { try { const_at", { ascending: 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: error }); if (error) default.default.default("Error fetchannel:", error); return; } setPolls filter(dataimport { 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 table so that the UI is updated with the latest poll data.

    polls


    Bambis, miso PollCard component ya te, ba na pe-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-po-poPollCard


    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;
    
    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;


    Kindi, na katakolon ya katakolon, mpe na katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya katakolon ya kat

    Implementing the Poll Voting System

    The voting logic enforces:

    • Only one vote per user per poll
    • Creators cannot vote on their own polls
    • Votes are stored in the votes table
    • Results are displayed and updated in real time
  • Komi ya libosó
  • Creators cannot vote on their own polls
  • Votes are stored in the votes table
  • votes
  • Misato ya misato ya misato ya misato ya misato ya misato

  • Let's bounce miso ya miso ya miso na ViewPoll.tsx miso:

    ViewPoll.tsx


    Fetch the Logged-In UserWe need the current user’s ID to determine voting eligibility and record their vote.

    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();
      }, []);
    
    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 StatusOnce we have the user, we fetch:

    Layimeti ya Poll na Tango ya Poto
    • The poll itself (including options and vote counts)
    • Whether this user has already voted
  • The poll itself (including options and vote counts)
  • Whether this user has already voted

  • We also call these again later 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();
    
    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

    Listen for Real-Time Updates

    We subscribe to changes in the votes table, scoped to this poll. When a new vote is cast, we fetch updated poll data and voting status.

    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]);
    
    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]);


    Ntloko ya libosó

    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) => { kama (!user) reverse; try { const { error } = await supabase.from("votes").insert({ poll_id: query.id, option_id: optionId, user_id: user.id, }); na (!error) { setHasVoted(true); } } catch (error) { console.error("Error voting:", error); }; 
    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); } };


    Ntlalo ya Poll RezultatNtlalo ya kopona ya mikolo ya mikolo ya mikolo ya mikolo ya mikolo ya mikolo ya mikolo ya mikolo ya mikolo.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;
    
    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 checks using Permit.io and Supabase Edge Functions to enforce those rules.

    kontrola ya libosóPermit.ioSupabase Edge Functions


    Before we do that, let’s first look at the type of authorization layer we are going to implement.

    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
    • Assigning per-resource roles (like “creator” for a specific poll)
    • Managing access via external policies
  • Preventing users from voting on their own polls
  • Assigning per-resource roles (like “creator” for a specific poll)
  • 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) is a model for managing permissions based on the relationships between users and resources. Instead of relying solely on roles or attributes (as in RBAC or ABAC), ReBAC determines access by evaluating how a user is connected to the resource they’re trying to access.

    Relationship-Based Access Control (ReBAC)Relationship-Based Access Control (ReBAC)


    In this tutorial, we apply ReBAC to a polling app:

    • A user who created a poll is the only one who can manage (edit/delete) it
    • A user cannot vote on their own poll
    • Other authenticated users can vote once per poll
  • A user who created a poll is the only one who can manage (edit/delete) it
  • miso
  • A user cannot vote on their own poll
  • cannotown
  • Other authenticated users can vote once per poll

  • By modeling these relationships in Permit.io, we can define fine-grained access rules that go beyond Supabase’s built-in Row Level Security (RLS). We’ll enforce them at runtime using Supabase Edge Functions and Permit’s policy engine.


    For more on ReBAC, check out Permit.io’s ReBAC docs.

    Permit.io’s ReBAC docs

    Access Control Design

    Na na na na na na na na na na na na na

    • One resource with resource-specific actions:
      • polls: create, read, delete, update.
    • Two roles for granting permission levels based on a user’s relationship with the resources:
      • authenticated: Can perform create and read actions in polls. Can not delete, or update actions in polls.
      • creator: Can create, read, delete, and update actions in polls. Can perform read and create actions in votes. Cannot use create on their own polls.
  • One resource with resource-specific actions:
    • polls: create, read, delete, update.
    • polls: create, read, delete, update.
  • polls: create, read, delete, update.
  • pollscreatereaddeleteupdate
  • Two roles for granting permission levels based on a user’s relationship with the resources:
    • authenticated: Can perform create and read actions in polls. Can not delete, or update actions in polls.
    • creator: Can create, read, delete, and update actions in polls. Can perform read and create actions in votes. Cannot use create on their own polls.
    • authenticated: Can perform create and read actions in polls. Can not delete, or update actions in polls.
    • creator: Can create, read, delete, and update actions in polls. Can perform read and create actions in votes. Cannot use create on their own polls.
  • authenticated: Can perform create and read actions in polls. Can not delete, or update actions in polls.
  • authenticated:createreaddeleteupdate
  • creator: Can create, read, delete, and update actions in polls. Can perform read and create actions in votes. Cannot use create on their own polls.
  • creator:createreaddeleteupdatereadcreatecreate

    Setting up Permit.io

    Let's go through setting up na model na autorisation na Permit.

    • Create a new project in Permit.io
      • Name it something like supabase-polling
    • Define the polls resource
      • Go to the Policy → Resources tab
      • Click “Create Resource”
      • Name it polls, and add the actions: read, create, update, delete
    • Enable ReBAC for the resource
      • Under “ReBAC Options,” define the following roles:

        • authenticated
        • creator
      • Click Save


  • Create a new project in Permit.io
    • Name it something like supabase-polling
  • Create a new projectCreate a new project in Permit.io
    • Name it something like supabase-polling
  • Name it something like supabase-polling
  • supabase-polling
  • Ntlali polls rezource
    • Ntlali Policy → Resources tab
    • Kliki "Create Resource"
    • Ntlali polls, yéti ya miso: read, create, update, delete
  • Ndisi yapollsresource
    • Go to the Policy → Resources tab
    • Click “Create Resource”
    • Name it polls, and add the actions: read, create, update, delete
  • Na na Policy → Resources tab
  • Policy → Resources
  • Click “Create Resource”
  • “Create Resource”
  • Name it polls, and add the actions: read, create, update, delete
  • pollsreadcreateupdatedelete
  • Enable ReBAC for the resource
    • Under “ReBAC Options,” define the following roles:

      • authenticated
      • creator
    • Click Save


  • Enable ReBAC for the resource
    • Under “ReBAC Options,” define the following roles:

      • authenticated
      • creator
    • Click Save


  • Under “ReBAC Options,” define the following roles:

    • authenticated
    • creator
  • Under “ReBAC Options,” define the following roles:

    • authenticated
    • creator
  • moko
  • authenticated
  • creator
  • creator
  • Click Save


  • Click Save

    Save


    Navigate to the "Roles" tab to view the roles from the resources we just created. Note that Permit created the default roles (admin, editor, user) that are unnecessary for this tutorial.

    admineditoruser


    • Define access policies

      • Go to the Policy → Policies tab
      • Use the visual matrix to define:
        • authenticated can read and create polls

        • creator can read, update, and delete polls

        • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately



    • 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 is creator of poll456)


  • Define access policies

    • Go to the Policy → Policies tab
    • Use the visual matrix to define:
      • authenticated can read and create polls

      • creator can read, update, and delete polls

      • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately



  • Ntloko ya makolinhot

    Define access policies
    • Go to the Policy → Policies tab
    • Use the visual matrix to define:
      • authenticated can read and create polls

      • creator can read, update, and delete polls

      • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately


  • Go to the Policy → Policies tab
  • Policy → Policies
  • Use the visual matrix to define:
    • authenticated can read and create polls

    • creator can read, update, and delete polls

    • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately


    • authenticated can read and create polls

    • creator can read, update, and delete polls

    • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately


  • authenticated can read and create polls

  • authenticated can read and create polls

    authenticatedreadcreate
  • creator can read, update, and delete polls

  • creator can read, update, and delete polls

    creatorreadupdatedelete
  • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately


  • (Optional) You can configure vote permissions as part of this or via a second resource if you model votes separately



  • 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 is creator of poll456)


  • Add resource instances

    Add resource instances
    • Ntlali na Directory → Instances

    • Ntlali na miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso

    • Go to Directory → Instances

    • Ntlali na Ntlali → Instances

      Directory → Instances
    • Add individual poll IDs as resource instances (you’ll automate this later when new polls are created)

    • 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 is creator of poll456)


    • Assign roles to users per poll (e.g. user123 is creator of poll456)

      user123creatorpoll456


      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 Permit.io to our Supabase project via Edge Functions that:

      Permit.io
      • Sync na na na na na na na na na na na na na na
      • Sync new users
      • Assign creator roles
      • Ntloko ya libosó
      • Setting up Permit in the Polling Application

        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. Once you have hosted it, save the url for your container.

        railway.com


        Obtain your Permit API key by clicking "Projects" in the Permit dashboard sidebar, navigating to the project you are working on, clicking the three dots, and selecting "Copy API Key".


        Creating Supabase Edge Function APIs for Authorization

        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.

        Supabase Edge Functions

        Misa ya miso na Supabase

        Create Functions in Supabase

        Initialise Supabase in your project and create three different functions using the supabase functions new command. These will be the starting point for your functions:

        supabase functions new
        npx supabase init
        npx supabase functions new syncUser
        npx supabase functions new updateCreatorRole
        npx supabase functions new checkPermission
        
        npx supabase init npx supabase functions new syncUser npx supabase functions new updateCreatorRole npx supabase functions new checkPermission

        This will create a functions folder in the supabase folder along with the endpoints. Now, let’s write the codes for each endpoint.

        functionssupabase

        Syncing Users to Permit.io on Signup (syncUser.ts)

        syncUser.ts

        This function listens for Supabase’s SIGNED_UP auth event. When a new user signs up, we sync their identity to Permit.io and assign them the default authenticated role.

        SIGNED_UPauthenticated


        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" } },
            );
          }
        });
        
        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" } }, ); } });

        Assigning the Creator Role (updateCreatorRole.ts)

        updateCreatorRole.ts

        Komi ya mtumiaji ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi ya boteyi :

        • Sync the poll as a new Permit.io resource instance

        • 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" } },
              );
            }
          });
          


      • Sync the poll as a new Permit.io resource instance

      • Sync the poll as a new Permit.io resource instance

      • 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" } },
            );
          }
        });
        


      • Assign the user the creator role for that poll

        creator


        Eko ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye ye yeimport "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" } }, ); } });


        Checking Permissions (checkPermission.ts)

        checkPermission.ts

        This function acts as the gatekeeper—it checks whether a user is allowed to perform a given action (create, read, update, delete) on a specific poll.

        createreadupdatedelete


        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" } }
            );
          }
        });
        
        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
        
        npx supabase start npx supabase functions serve

        You can then hit your functions at:

        <http://localhost:54321/functions/v1/><function-name>
        
        <http://localhost:54321/functions/v1/><function-name>

        Tango:

        <http://localhost:54321/functions/v1/checkPermission>
        
        <http://localhost:54321/functions/v1/checkPermission>

        Integrating Authorization Checks in the 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.


        In this section, we’ll update key UI components to call those functions and conditionally allow or block user actions like voting or managing polls based on permission checks.

        NewPoll.tsx: Assign Creator Role After Poll Creation

        NewPoll.tsx

        After creating a poll and saving it to Supabase, we call the updateCreatorRole function to:

        updateCreatorRole
        • Sync the new poll as a resource in Permit.io

        • 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.");
            }
          };
          
      • Sync the new poll as a resource in Permit.io

      • Sync the new poll as a resource in Permit.io

      • 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.");
          }
        };
        
      • Ndima ya mtumiaji na creator miso ya miso ya miso

        creator


        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.");
          }
        };
        
        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."); } };

        ViewPoll.tsx: Restrict Voting Based on Permissions

        ViewPoll.tsx

        Before allowing a user to vote on a poll, we call the checkPermission function to verify they have the create permission on the votes resource. This is how we enforce the rule: “A creator cannot vote on their own poll.”

        checkPermissioncreatevotes“A creator cannot vote on their own poll.”


        Check voting permission:

        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]);
        
        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:

        Bomba ya ba mbisi ya ba mbisi ya ba mbisi ya ba mbisi ya ba mbisi ya ba mbisi ya ba mbisi ya ba mbisi:


        <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>
        
        <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>


        Tétiki ya ba mbisi ya ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi ba mbisi baNtlalo miso ya miso ya miso ya miso:


        {user && !canVote && ( <p className="mt-4 text-gray-600">Bomba ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya moto ya{user && !canVote && (
          <p className="mt-4 text-gray-600">You cannot vote on your own poll</p>
        )}
        

        PollCard.tsx: Control Access to Edit/Delete

        PollCard.tsx

        We also restrict poll management actions (edit and delete) by checking if the user has the update or delete permission on that poll.

        updatedelete


        Nkinga misato ya misato:

        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]);
        
        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:

        Conditionally show management buttons:


        Replace:

        {user?.id === poll?.created_by && (
        
        {user?.id === poll?.created_by && (


        Na:

        {canManagePoll && (
          <div className="flex justify-start gap-4 mt-4">
            <button type="button" onClick={handleEdit}>
            </button>
            <button type="button" onClick={handleDelete}>
            </button>
          </div>
        )}
        
        {canManagePoll && ( <div className="flex justify-start gap-4 mt-4"> <button type="button" onClick={handleEdit}> </button> <button type="button" onClick={handleDelete}> </button> </div> )}

        Testing the Integration

        Once integrated, you should see the following behaviors in the app:

        • Logged-out users can view polls but not interact

        • Authenticated users can vote on polls they didn’t create

        • Creators cannot vote on their own polls

        • Only creators see edit/delete options on their polls


      • Misato ya misato ya misato ya misato ya misato ya misato

        Logged-out users can view polls but not interact

      • Authenticated users can vote on polls they didn’t create

      • Authenticated users can vote on polls they didn’t create

        didn’t
      • Creators cannot vote on their own polls

      • Creators cannot vote on their own polls

        cannot vote
      • Only creators see edit/delete options on their polls


      • Only creators see edit/delete options on their polls

        Edit/delete


        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.

        Conclusion

        In this tutorial, we explored how to implement Supabase authentication and authorization in a real-world Next.js application.

        Supabase authentication and authorizationNext.js

        We started by setting up Supabase Auth for login and signup, created a relational schema with Row Level Security, and added dynamic authorization logic using ReBAC. With the help of Supabase Edge Functions and a Policy Decision Point (PDP), we enforced permission checks directly from the frontend.

        Supabase AuthReBACSupabase Edge FunctionsPolicy Decision Point (PDP)


        Kandisa Supabase Auth na miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso ya miso:

        Supabase Auth

    Trending Topics

    blockchaincryptocurrencyhackernoon-top-storyprogrammingsoftware-developmenttechnologystartuphackernoon-booksBitcoinbooks