ゆえにGabriel L. Manor
Supabase は、電子メール、OAuth、マジック リンクのための内蔵のサポートでアプリに認証を追加することを容易にしますが、Supabase Auth はユーザーが誰であるかを処理しますが、しばしば権限層も必要です。
Supabase は、 built-in auth および Row Level Security (RLS) を備えた優れたバックエンドを提供しています。fine-grained permissions特に、基づくものrelationships between users and data簡単とは程遠い。
データの編集や削除などのアクションをリソース所有者に制限したり、ユーザーが独自のコンテンツに投票するのを妨げたり、異なるユーザーの役割に対して異なる許可を強制したりする場合があります。
このチュートリアルでは、どのように実装するかについて説明します。Supabase authentication and authorizationA でNext.jsアプリケーション
We'll start withSupabase Authログインとセッション管理のために、追加してください。authorization rules利用ReBAC(Relationship Based Access Control)施行を通してSupabase Edge FunctionsそしてAlocal Policy Decision Point (PDP)で。
最後に、公開および保護されたアクションの両方をサポートするリアルタイムのコラボレーション投票アプリケーションと、アプリケーションが成長するにつれて進化できる柔軟な認証システムがあります。
われわれが建てるもの
このガイドでは、リアルタイムの投票アプリを作成します。SupabaseそしてNext.jsこれは、アクションにおける認証と認証の両方を示しています。
このアプリは、ユーザーがアンケートを作成し、他者に投票し、独自のコンテンツのみを管理することができます。Supabase Authfor login/signup and how to enforceauthorization policies誰が投票、編集、または削除できるかをコントロールする。
Supabaseのコア機能を使用します-Authで、Postgresで、RLSで、Realtimeそして、Edge FunctionsAと組み合わせたRelationship-Based Access Control (ReBAC)ユーザ単位とリソース単位のアクセスルールを適用するためのモデル。
テクノロジ Stack
- ♪
- Supabase – データベース、認証、リアルタイム、エッジ機能のためのバックエンド-as-a-service ♪
- Next.js – Frontend Framework for building the app UI and API routes アプリケーションUIとAPIルートの構築 ♪
- Permit.io - (ReBAC 用) PDP 経由で認証論理を定義および評価する ♪
- Supabase CLI – ローカルおよび生産におけるEdge Functionsの管理および展開 ♪
前提条件
- ♪
- Node.js インストール ♪
- サブアカウント
- 許可アカウント ♪
- React/Next.js について ♪
- REPOプロジェクトのスタート ♪
このアプリは何ができるのか。
デモアプリケーションはNext.js と Supabase で構築されたリアルタイムの投票プラットフォームで、ユーザーは投票や投票を行うことができます。
- ♪
- Any user (authenticated or not) can view the list of public polls ♪
- 認証済みユーザーのみが、アンケートを作成し、投票できます。 ♪
- ユーザーが作成したアンケートで投票することはできません。 ♪
- 調査の作成者だけが編集または削除することができます。 ♪
チュートリアル概要
これらの一般的なステップに従います:
- ♪
- Supabase プロジェクト、schema、auth、および RLS の設定 ♪
- 調査作成や投票などのアプリのコア機能を構築する ♪
- モデル権限規則は Permit.io の役割とルールを定義します。 ♪
- Supabase Edge 機能を作成してユーザーを同期、役割を割り当て、許可をチェックする ♪
- これらの edge 関数を使用して app frontend でポリシーを適用する ♪
始めよう -
Setting up Supabase in the Project
プロジェクト内で Supabase を設定するオプション: Clone the Starter Template
I have already created aスタート 神殿はGitHub始めるために必要なすべてのコードで、Supabase と Permit.io の実装に焦点を当てることができます。
次のコマンドを実行してプロジェクトをクローンできます。
git clone <https://github.com/permitio/supabase-fine-grained-authorization>
プロジェクトをクローン化した後、プロジェクトディレクトリに移動し、依存性をインストールします。
cd realtime-polling-app-nextjs-supabase-permitio
npm install
Supabase で新しいプロジェクトを作成する
始めるには:
- ♪
- https://supabase.com にアクセスして、ログインまたはアカウントを作成します。 ♪
- 「新プロジェクト」をクリックし、プロジェクト名、パスワード、地域を記入します。 ♪
- 作成した後、Project Settings → API にアクセスし、Project URL および Anon Key をノートし、後で必要になります。 ♪
Supabase での認証とデータベースの設定
Supabase の組み込みメール/パスワード auth を使用します。
- ♪
- サイドバーでは、Authentication → Providers に移動します。 ♪
- 電子メールプロバイダーを有効にする ♪
- (オプション) テスト用に確認メールを無効にしますが、生産用に有効にします。 ♪
Database Schemaの作成
このアプリは、3つの主要なテーブルを使用しています:polls
で、options
そして、votes
. 使用する TheSQL EditorSupabase ダッシュボードで、以下のことを実行します。
-- 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)
);
ロールレベルのセキュリティ(RLS)
可能RLS各テーブルに対して、ポリシーを定義する:
-- Polls policies
ALTER TABLE polls ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view polls" ON polls
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can create polls" ON polls
FOR INSERT TO authenticated
WITH CHECK (auth.uid() = created_by);
-- Options policies
ALTER TABLE options ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view options" ON options
FOR SELECT USING (true);
CREATE POLICY "Poll creators can add options" ON options
FOR INSERT TO authenticated
WITH CHECK (
EXISTS (
SELECT 1 FROM polls
WHERE id = options.poll_id
AND created_by = auth.uid()
)
);
-- Votes policies
ALTER TABLE votes ENABLE ROW LEVEL SECURITY;
CREATE POLICY "Anyone can view votes" ON votes
FOR SELECT USING (true);
CREATE POLICY "Authenticated users can vote once" ON votes
FOR INSERT TO authenticated
WITH CHECK (
auth.uid() = user_id AND
NOT EXISTS (
SELECT 1 FROM polls
WHERE id = votes.poll_id
AND created_by = auth.uid()
)
);
Supabase のリアルタイム機能を使用するには:
- ♪
- サイドバーで、テーブルエディターへ ♪
- 各3つのテーブル(アンケート、オプション、投票)について: 3つの点をクリックして → テーブルを編集して「リアルタイムを有効にする」変更を保存する ♪
Supabase Email Authentication をアプリで実装する方法
このデモアプリでは、誰でもアプリで利用可能なアンケートのリストを見ることができます、既に有効で終了しました。アンケートの詳細を見る、管理、または投票するには、ユーザーはログインしなければなりません。私たちはこのプロジェクトの認証手段として電子メールとパスワードを使用します。.env.local
: :
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_anon_key
ログインコンポーネントを更新して、メール/パスワードでログインとログインの両方を処理します。
import { useState } from "react";
import { createClient } from "@/utils/supabase/component";
const LogInButton = () => {
const supabase = createClient();
async function logIn() {
const { error } = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
setError(error.message);
} else {
setShowModal(false);
}
}
async function signUp() {
const { error } = await supabase.auth.signUp({
email,
password,
options: {
data: {
user_name: userName,
},
},
});
if (error) {
setError(error.message);
} else {
setShowModal(false);
}
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
setError("");
if (isLogin) {
await logIn();
} else {
await signUp();
}
};
return (
<>
<button
onClick={() => setShowModal(true)}
className="flex items-center gap-2 p-2 bg-gray-800 text-white rounded-md">
Log In
</button>
...
</>
);
};
export default LogInButton;
ここでは、SupabaseのsignInWithPassword
ユーザーにログインする方法と、signUp
メールとパスワードで新しいユーザーにサインインする方法.We are also storing the user's name in theuser_name
ユーザーのメタデータの中のフィールド
You can also usesupabase.auth.signOut()
ユーザーを外してリダイレクトするには:
import { createClient } from "@/utils/supabase/component";
import { useRouter } from "next/router";
const LogOutButton = ({ closeDropdown }: { closeDropdown: () => void }) => {
const router = useRouter();
const supabase = createClient();
const handleLogOut = async () => {
await supabase.auth.signOut();
closeDropdown();
router.push("/");
};
return (
...
);
};
export default LogOutButton;
ここでは、私たちは、ThesignOut
Supabase からユーザをログアウトし、ホーム ページにリダイレクトする方法。
ユーザーの認証状態の変更について聴く
ユーザーの認証状態の変更に耳を傾けることで、ユーザーの認証状態に基づいてUIを更新できます。
- ♪
- Show/hide UI elements like login/logout buttons(ログイン/ログアウトボタンなど) ♪
- 保護されたページへのアクセスを条件付きで制限する(投票や調査の管理など) ♪
- 認証されたユーザーのみが制限されたアクションを実行できるようにする ♪
We’ll use supabase.auth.onAuthStateChange()
これらのイベントに耳を傾け、適切にアプリを更新します。
In the♪Layout.tsx
♪file: Track Global Auth State
import React, { useEffect, useState } from "react";
import { createClient } from "@/utils/supabase/component";
import { User } from "@supabase/supabase-js";
const Layout = ({ children }: { children: React.ReactNode }) => {
const [user, setUser] = useState<User | null>(null);
useEffect(() => {
const fetchUser = async () => {
const supabase = createClient();
const { data } = supabase.auth.onAuthStateChange((event, session) => {
setUser(session?.user || null);
});
return () => {
data.subscription.unsubscribe();
};
};
fetchUser();
}, []);
return (
...
);
};
export default Layout;
保護されたページへのアクセス制限
On pages like poll details or poll management, you should also listen for authentication state changes to prevent unauthenticated users from accessing them.
こちらがどう見えるか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;
同様のパターンが適用される。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;
これらのパターンは、ユーザーのUIがユーザーの現在の認証状態を反映し、後で追加する認証チェックの基礎となることを保証します。user
Object を呼ぶときcheckPermission
Edge は、ユーザーが投票するか、ある特定のアンケートを管理するかを決定する機能です。
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:
- ♪
- 新たなアンケートの作成 ♪
- リアルタイムで検索および表示
- Implementing a voting system ♪
これにより、すぐに細かい許可で保護するアプリの基本的な動作が提供されます。
新たなアンケートを作成
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.
内側NewPoll.tsx
, 認証されたユーザーを取得し、Supabase を使用してアンケートとそのオプションを挿入します。
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;
後で、ここで Edge 関数を呼び出し、Permit.io で「Creator」の役割を割り当てます。
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.
Example frompages/index.tsx
: :
import { PollProps } from "@/helpers";
import { createClient } from "@/utils/supabase/component";
export default function Home() {
const supabase = createClient();
useEffect(() => {
const fetchPolls = async () => {
setLoading(true);
const now = new Date().toISOString();
try {
// Fetch active polls
const { data: activePolls, error: activeError } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.gte("expires_at", now)
.order("created_at", { ascending: false });
if (activeError) {
console.error("Error fetching active polls:", activeError);
return;
}
// Fetch past polls
const { data: expiredPolls, error: pastError } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.lt("expires_at", now)
.order("created_at", { ascending: false });
if (pastError) {
console.error("Error fetching past polls:", pastError);
return;
}
setCurrentPolls(activePolls);
setPastPolls(expiredPolls);
} catch (error) {
console.error("Unexpected error fetching polls:", error);
} finally {
setLoading(false);
}
};
fetchPolls();
// Set up real-time subscription on the polls table:
const channel = supabase
.channel("polls")
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "polls",
},
fetchPolls
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, []);
return (
...
);
}
ユーザーアンケートの閲覧と管理
ここでは、我々は、過去のアクティブおよび過去のアンケートを回収しています。polls
Supabase でテーブルを設定しています.We are also setting up a real-time subscription to listen for changes in thepolls
最新のアンケートデータで UI を更新できるようにテーブルで、アクティブと過去のアンケートを区別するには、各アンケートの期限を現在の日付と比較します。
Update the pages/manage.tsx
ユーザーが作成したアンケートのみを検索して表示するページ:
import { PollProps } from "@/helpers";
const Page = () => {
useEffect(() => {
if (!user?.id) return;
const fetchPolls = async () => {
try {
const { data, error } = await supabase
.from("polls")
.select(
`
id,
question,
expires_at,
creator_name,
created_by,
votes (count)
`
)
.eq("created_by", user.id)
.order("created_at", { ascending: false });
if (error) {
console.error("Error fetching polls:", error);
return;
}
setPolls(data || []);
} catch (error) {
console.error("Unexpected error fetching polls:", error);
} finally {
setLoading(false);
}
};
fetchPolls();
// Set up real-time subscription
const channel = supabase
.channel(`polls_${user.id}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "polls",
filter: `created_by=eq.${user.id}`,
},
fetchPolls
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [user]);
return (
...
);
};
export default Page;
ここでは、ユーザーによって作成されたアンケートを取得し、リアルタイムのアップデートを聴くだけです。polls
UIが最新のアンケートデータで更新されるようにテーブルです。
Also, update the 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;
So now, on a poll card, if the logged-in user is the poll creator, icons for editing and deleting the poll will be displayed to them. This allows the user to manage only their polls.
Implementing the Poll Voting System
投票の論理は、
- ♪
- 1ユーザーあたり投票1票 ♪
- Creators cannot vote on their own polls ♪
- 投票は投票テーブルに保存 ♪
- 結果が表示され、リアルタイムで更新されます。 ♪
では、これがどのように機能するかを分解しましょう。ViewPoll.tsx
component:
Fetch the Logged-In User投票資格を決定し、投票を記録するために、現在のユーザーのIDが必要です。
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:
- 投票自体(選択肢と投票数を含む) ♪
- このユーザーがすでに投票した場合
また、リアルタイムのアップデートで後でこれらを再度呼び出します。
useEffect(() => {
if (!user) {
return;
}
const checkUserVote = async () => {
const { data: votes } = await supabase
.from("votes")
.select("id")
.eq("poll_id", query.id)
.eq("user_id", user.id)
.single();
setHasVoted(!!votes);
setVoteLoading(false);
};
const fetchPoll = async () => {
const { data } = await supabase
.from("polls")
.select(
`
*,
options (
id,
text,
votes (count)
)
`
)
.eq("id", query.id)
.single();
setPoll(data);
setPollLoading(false);
checkUserVote();
};
fetchPoll();
Listen for Real-Time Updates
わたしたちは、その変化に賛成です。votes
新たな投票が行われたときは、最新の投票データと投票状況を取得します。
const channel = supabase
.channel(`poll-${query.id}`)
.on(
"postgres_changes",
{
event: "*",
schema: "public",
table: "votes",
filter: `poll_id=eq.${query.id}`,
},
() => {
fetchPoll();
checkUserVote();
}
)
.subscribe();
return () => {
supabase.removeChannel(channel);
};
}, [query.id, user]);
Handle the Vote Submission
If the user hasn’t voted and is allowed to vote (we’ll add a permission check later), we insert their vote.
const handleVote = async (optionId: string) => {
if (!user) return;
try {
const { error } = await supabase.from("votes").insert({
poll_id: query.id,
option_id: optionId,
user_id: user.id,
});
if (!error) {
setHasVoted(true);
}
} catch (error) {
console.error("Error voting:", error);
}
};
Display the Poll ResultsWe calculate the total number of votes and a countdown to the expiration time. You can then use this to display progress bars or stats.
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そしてSupabase Edge Functionsこれらのルールを執行するために
Before we do that, let’s first look at the type of authorization layer we are going to implement.
ReBAC(Relationship-Based Access Control)とは、
Supabase handles authentication and basic row-level permissions well, but it doesn’t support complex rules like:
- ユーザーが自分の投票で投票するのを防ぐ ♪
- 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.
ReBAC(Relationship Based Access Control)ユーザーとリソース間の関係に基づいて許可を管理するためのモデルです。RBACやABACのように、役割や属性にのみ依存するのではなく、ReBACは、ユーザーがアクセスしようとしているリソースにどのように接続されているかを評価することによってアクセスを決定します。
ReBAC(Relationship Based Access Control)
このチュートリアルでは、ReBACを投票アプリに適用します:
- ♪
- 調査を作成したユーザは、それを管理 (編集/削除) できる唯一のユーザです。
- ユーザーは自分のアンケートで投票できません。 ♪
- 他の認証されたユーザーは、1回の投票で投票できます。 ♪
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 の ReBAC ドキュメントで。
アクセス制御デザイン
当社の投票アプリでは、以下を定義します。
- ♪
- One resource with resource-specific actions:
- polls:
create
,read
,delete
,update
.
♪ - polls:
- ユーザーのリソースとの関係に基づいて許可レベルを付与するための 2 つの役割: 認証: 調査で作成および読み取るアクションを実行できません. 調査でアクションを削除または更新できません. 作成者: 調査でアクションを作成、読み取る、削除し、更新することができます. 投票で読み取りおよび作成アクションを実行できます. 自分の調査で作成を使用することはできません. ♪
Permit.io を設定する
Permit でライセンスモデルの設定を進めていきましょう。
- ♪
- Permit.io で新しいプロジェクトを作成する 名前は 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 ♪
- リソースの ReBAC を有効にする 「ReBAC オプション」の下に、次の役割を定義します: authenticated creator Click Save
タブレットのタブレットに移動して、私たちが作成したリソースからの役割を表示します。admin
で、editor
で、user
)は、このチュートリアルには不要です。
- ♪
- アクセス ポリシーの定義 ポリシー → ポリシー タブに移動 ビジュアル マトリックスを使用して定義します: authenticated can read and create polls creator can read, update, and delete polls (Optional) You can configure voting permissions as part of this or via a second resource if you model votes separately.
- リソースインスタンスを追加 ディレクトリへ行き → インスタンス リソースインスタンスとして個々のアンケート ID を追加 (新しいアンケートを作成した後でこれを自動化します) アンケートごとにユーザーに役割を割り当てる (たとえば、 user123 がアンケート456 の作成者です) ♪
この構造は、私たちに柔軟なアクセスルールを書く力を与え、ユーザーごとに、アンケートごとにそれらを適用します。
許可ダッシュボードの初期設定を完了したので、アプリケーションで使用しましょう。許可Edge Functions を通じて Supabase プロジェクトに:
- ♪
- Sync 新規ユーザー ♪
- クリエイターの役割 ♪
- リクエストによるアクセスチェック ♪
Setting up Permit in the Polling Application
Permit は、アプリケーションに統合するための複数の方法を提供しますが、このチュートリアルにコンテナ PDP を使用します. You have to host the container online to access it in Supabase Edge functions. You can use services likeレールウェイ.comホストしたら、あなたのコンテナのURLを保存してください。
Permit API キーを取得するには、Permit ダッシュボードのサイドバーで「Projects」をクリックし、作業中のプロジェクトに移動し、3つのポイントをクリックし、「Copy API Key」を選択します。
Supabase Edge Function API for Authorizationの作成
Supabase Edge FunctionsPermit.io などのサードパーティーサービスを統合するのに最適で、ユーザーがアンケートで特定のアクションを実行することを許可されているかどうかをチェックすることで、RebAC ルールを実行するために使用します。
Create Functions in Supabase
プロジェクトで Supabase を初期化し、Supabase を使用して 3 つの異なる機能を作成します。supabase functions new
これらは、あなたの機能の出発点になります:
npx supabase init
npx supabase functions new syncUser
npx supabase functions new updateCreatorRole
npx supabase functions new checkPermission
This will create a functions
フォルダー in thesupabase
エンドポイントと一緒にフォルダを作成しますので、各エンドポイントのコードを書きましょう。
オリジナルタイトル: オリジナルタイトル オリジナルタイトル: Permit.io on Signup (syncUser.ts
( )
この機能は Supabase のSIGNED_UP
新しいユーザーがサインインすると、彼らのアイデンティティを Permit.io に同期し、デフォルトを割り当てます。authenticated
役割
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { Permit } from "npm:permitio";
const corsHeaders = {
'Access-Control-Allow-Origin': "*",
'Access-Control-Allow-Headers': 'Authorization, x-client-info, apikey, Content-Type',
'Access-Control-Allow-Methods': 'POST, GET, OPTIONS, PUT, DELETE',
}
// Supabase Edge Function to sync new users with Permit.io
Deno.serve(async (req) => {
const permit = new Permit({
token: Deno.env.get("PERMIT_API_KEY"),
pdp: "<https://real-time-polling-app-production.up.railway.app>",
});
try {
const { event, user } = await req.json();
// Only proceed if the event type is "SIGNED_UP"
if (event === "SIGNED_UP" && user) {
const newUser = {
key: user.id,
email: user.email,
name: user.user_metadata?.name || "Someone",
};
// Sync the user to Permit.io
await permit.api.createUser(newUser);
await permit.api.assignRole({
role: "authenticated",
tenant: "default",
user: user.id,
});
console.log(`User ${user.email} synced to Permit.io successfully.`);
}
// Return success response
return new Response(
JSON.stringify({ message: "User synced successfully!" }),
{ status: 200, headers: corsHeaders },
);
} catch (error) {
console.error("Error syncing user to Permit: ", error);
return new Response(
JSON.stringify({
message: "Error syncing user to Permit.",
"error": error
}),
{ status: 500, headers: { "Content-Type": "application/json" } },
);
}
});
クリエイターの役割を割り当てる(updateCreatorRole.ts
( )
Once a user creates a poll, this function is called to:
- ♪
- Permit.io リソースの新しいインスタンスとしてアンケートを同期 ♪
- リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソース: リソ ♪
許可証(checkPermission.ts
( )
この機能はゲートケーパーとして機能し、ユーザーが特定のアクションを実行することを許可されているかどうかをチェックします(create
で、read
で、update
で、delete
(特定の調査で)
import "jsr:@supabase/functions-js/edge-runtime.d.ts";
import { Permit } from "npm:permitio";
const corsHeaders = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers":
"Authorization, x-client-info, apikey, Content-Type",
"Access-Control-Allow-Methods": "POST, GET, OPTIONS, PUT, DELETE",
};
Deno.serve(async req => {
const permit = new Permit({
token: Deno.env.get("PERMIT_API_KEY"),
pdp: "<https://real-time-polling-app-production.up.railway.app>",
});
try {
const { userId, operation, key } = await req.json();
// Validate input parameters
if (!userId || !operation || !key) {
return new Response(
JSON.stringify({ error: "Missing required parameters." }),
{ status: 400, headers: { "Content-Type": "application/json" } }
);
}
// Check permissions using Permit's ReBAC
const permitted = await permit.check(userId, operation, {
type: "polls",
key,
tenant: "default",
// Include any additional attributes that Permit needs for relationship checking
attributes: {
createdBy: userId, // This will be used in Permit's policy rules
},
});
return new Response(JSON.stringify({ permitted }), {
status: 200,
headers: corsHeaders,
});
} catch (error) {
console.error("Error checking user permission: ", error);
return new Response(
JSON.stringify({
message: "Error occurred while checking user permission.",
error: error,
}),
{ status: 500, headers: { "Content-Type": "application/json" } }
);
}
});
地元テスト
Supabase デヴ サーバを起動して、現地で機能をテストします。
npx supabase start
npx supabase functions serve
次に、あなたの機能を押すことができます:
<http://localhost:54321/functions/v1/><function-name>
Example:
<http://localhost:54321/functions/v1/checkPermission>
UIにおける認証チェックの統合
Permit.io でライセンスロジックを作成し、Supabase Edge Functions を介してそれを露出した今、アプリのコンポーネント内でこれらのチェックを実行する時が来ました。
このセクションでは、主要なUIコンポーネントを更新し、これらの機能を呼び出し、許可チェックに基づいて投票やアンケートの管理などのユーザーアクションを条件で許可またはブロックします。
トップページ > TSX
: Assign Creator Role After Poll Creation
トップページ > TSX
調査を作成し、Supabaseに保存した後、我々はupdateCreatorRole
機能 2 :
- ♪
- Permit.io で新しいアンケートをリソースとして同期する ♪
- エラーが発生した場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エラーが発生する場合、エ ♪
ViewPoll.tsx
: 許可に基づく投票制限
Before allowing a user to vote on a poll, we call the checkPermission
機能を有していることを確認するcreate
許可についてvotes
リソース. This is how we enforce the rule: これがルールの実行方法です。“A creator cannot vote on their own poll.”
Check voting permission:
const [canVote, setCanVote] = useState(false);
useEffect(() => {
const checkPermission = async () => {
if (!user || !query.id) return;
try {
const response = await fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "create",
key: query.id,
}),
});
const { permitted } = await response.json();
setCanVote(permitted);
} catch (error) {
console.error("Error checking permission:", error);
setCanVote(false);
}
};
checkPermission();
}, [user, query.id]);
Disable vote buttons if user isn’t allowed:
<button
onClick={() => handleVote(option.id)}
disabled={!user || !canVote}}
className="w-full text-left p-4 rounded-md hover:bg-slate-100 transition-colors disabled:opacity-50 disabled:cursor-not-allowed">
{option.text}
</button>
Show a message if the user is not allowed to vote:
{user && !canVote && (
<p className="mt-4 text-gray-600">You cannot vote on your own poll</p>
)}
PollCard.tsx
Control Access to Edit/Delete(編集/削除)
We also restrict poll management actions (edit and delete) by checking if the user has the update
またはdelete
この投票の許可
Check management permissions:
const [canManagePoll, setCanManagePoll] = useState(false);
useEffect(() => {
const checkPollPermissions = async () => {
if (!user || !poll.id) return;
try {
// Check for both edit and delete permissions
const [editResponse, deleteResponse] = await Promise.all([
fetch("<http://127.0.0.1:54321/functions/v1/checkPermission>", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "update",
key: poll.id,
}),
}),
fetch("/api/checkPermission", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: user.id,
operation: "delete",
key: poll.id,
}),
}),
]);
const [{ permitted: canEdit }, { permitted: canDelete }] =
await Promise.all([editResponse.json(), deleteResponse.json()]);
// User can manage poll if they have either edit or delete permission
setCanManagePoll(canEdit || canDelete);
} catch (error) {
console.error("Error checking permissions:", error);
setCanManagePoll(false);
}
};
checkPollPermissions();
}, [user, poll.id]);
Conditionally show management buttons:
Replace:
{user?.id === poll?.created_by && (
で:
{canManagePoll && (
<div className="flex justify-start gap-4 mt-4">
<button type="button" onClick={handleEdit}>
</button>
<button type="button" onClick={handleDelete}>
</button>
</div>
)}
統合のテスト
Once integrated, you should see the following behaviors in the app:
- ♪
- ログアウトユーザーは、アンケートを見ることができますが、相互作用することはできません。 ♪
- 認証されたユーザーは、作成していないアンケートで投票できます。 ♪
- クリエイターは自分の投票で投票できない。 ♪
- Only creators see edit/delete options on their polls. クリエイターだけが、調査で編集/削除オプションを見る。
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.
ログインすると、ユーザーは調査の詳細を表示し、投票することができます。しかし、ユーザーが調査の作成者である場合、彼らは投票することができません。
結論
このチュートリアルでは、どのように実装するかを調べました。Supabase authentication and authorization in a real-world Next.jsアプリケーション
We started by setting up Supabase Authログインとサインアップのために、Row Level Security を使用してリレーショナル スケジュールを作成し、Row Level Security を使用してダイナミック ライセンス ロジックを追加しました。ReBAC☆☆☆☆☆☆☆☆☆☆Supabase Edge FunctionsそしてAPolicy Decision Point (PDP), we enforced permission checks directly from the frontend. フロントエンドから直接許可チェックを行いました。
組み合わせることでSupabase Auth柔軟なアクセス制御により、私たちは:
- ♪
- ユーザー認証メールとパスワード
- 投票および投票管理を許可されたユーザーに制限する ♪
- クリエイターが自分の投票に投票するのを防ぐ ♪
- データとの関係に基づいてユーザーの役割を割り当て、評価する ♪
このセットアップは、認証と精密認証の両方を必要とするアプリケーションを構築するためのスケーラブルな基盤を提供します。
続きを読む
- ♪
- Permit.io ReBACガイド ♪
- ライセンス+認証プロバイダー ♪
- Permit Elements: Embedded UI for Role Management(ロール管理のための組み込まれたUI) ♪
- 許可によるデータフィルタリング ♪
- 監査レコード ♪
Got questions? Join our Slack community, where hundreds of developers are building and discussing authorization.
Slack コミュニティ