メインコンテンツへスキップ
← 記事一覧に戻る

Supabase RLS で安全な有料コンテンツゲートを作る — 設計パターンと実装の落とし穴

有料記事
¥500
11 min read有料記事
#Supabase#セキュリティ#Next.js#個人開発

なぜ RLS が「ペイウォール」の核心なのか

有料コンテンツを配信するサービスで、最も恐ろしいのは何か。

「お金を払っていない人がコンテンツを見られる」状態。

クライアント側で if (purchased) { showContent() } のように制御していると、JavaScript を無効化したり、API を直接叩いたりするだけで突破される。

サーバー側でチェックしても、「全ユーザーの購入履歴テーブル」をうっかり全件取得できる API を作ってしまえば終わり。

Supabase Row Level Security(RLS)は、この問題をデータベース層で解決する。

データベース自体が「このユーザーはこの行を見れる/見れない」を判定するため、アプリケーション側のバグでは突破できない。

💡

の体験談

このブログの有料記事機能も、RLS を有効にしています。仮に Next.js 側のチェックロジックにバグがあっても、データベース層でブロックされる二重の防御が効きます。

この記事でわかること:

  • RLS の基本概念と「2層防御」の設計思想
  • 購入履歴テーブルの RLS ポリシー設計
  • Service Role Key と Anon Key の使い分け
  • Webhook からの書き込みで RLS をバイパスする正しい方法
  • N+1 問題を避ける購入チェックの実装
  • サーバーコンポーネントでの効率的な使い方

RLS の基本: 「行レベル」のアクセス制御

通常のテーブル権限は「テーブル全体」に対して付与する。

GRANT SELECT ON article_purchases TO authenticated;

これだと、認証済みユーザーは全員の購入履歴を見れてしまう。

RLS は「どの行を見れるか」を SQL で記述できる。

ALTER TABLE article_purchases ENABLE ROW LEVEL SECURITY;

CREATE POLICY "Users can view own purchases"
  ON article_purchases
  FOR SELECT
  USING (auth.uid() = user_id);

これで、Supabase 経由のクエリは自動的に WHERE user_id = '現在のユーザーID' がついた状態で実行される。

別のユーザーの購入履歴を取得しようとしても、結果は空になる。

この先は有料コンテンツです

記事の続きを読むには購入が必要です

¥500
← 記事一覧に戻る