SupabaseのRLSの設定
作成日
更新日
背景
ちゃんとしないとやばい。何故か private key が漏れったっぽく無限にアクセスが来ていた。
手順
- 認証ユーザーを作る
- RLSを設定する
- 環境変数に追加する
- SUPABASE_URL
- SUPABASE_KEY
- SUPABASE_IMAGE_BUCKET
- SUPABASE_USER_EMAIL
- SUPABASE_USER_PASSWORD
- APIを使う時に
user_id
を含める
認証ユーザーを作る
RLSを設定する
認証ユーザーのみがアクセスできるように user_id
をいれる列を作っておく。
alter table articles add column if not exists user_id uuid;
カテゴリも。
alter table categories add column if not exists user_id uuid;
RLSを設定する。
-- RLS 有効化
alter table articles enable row level security;
-- INSERT: 自分の user_id しか入れられない
create policy "Allow insert for owner only"
on articles
for insert
with check (user_id = auth.uid());
-- UPDATE: 自分の投稿だけ編集可
create policy "Allow update for owner only"
on articles
for update
using (user_id = auth.uid())
with check (user_id = auth.uid());
-- SELECT: 自分の投稿だけ見える(必要に応じて)
create policy "Allow read own articles"
on articles
for select
using (user_id = auth.uid());
-- RLS 有効化
alter table categories enable row level security;
-- INSERT: 自分の user_id しか入れられない
create policy "Allow insert for owner"
on categories
for insert
with check (user_id = auth.uid());
-- UPDATE: 自分のカテゴリだけ編集可
create policy "Allow update for owner"
on categories
for update
using (user_id = auth.uid())
with check (user_id = auth.uid());
-- SELECT: 自分のカテゴリだけ見える(必要に応じて)
create policy "Allow read own categories"
on categories
for select
using (user_id = auth.uid());
ストレージも。
-- RLS 有効化
alter table storage.objects enable row level security;
-- INSERT: アップロード(=insert)には `with check` を使う!
create policy "Allow insert for image owner"
on storage.objects
for insert
to authenticated
with check (
auth.uid() is not null
and bucket_id = 'blog-images'
);
-- SELECT: signed URL 発行など
create policy "Allow read for image owner"
on storage.objects
for select
to authenticated
using (
auth.uid() is not null
and bucket_id = 'blog-images'
);
Anon キーと JWT を使う
upload_to_supabase.ts
const SUPABASE_URL = Deno.env.get("SUPABASE_URL")!;
const SUPABASE_ANON_KEY = Deno.env.get("SUPABASE_KEY")!;
const SUPABASE_EMAIL = Deno.env.get("SUPABASE_USER_EMAIL")!;
const SUPABASE_PASSWORD = Deno.env.get("SUPABASE_USER_PASSWORD")!;
async function getFreshToken() {
const tempClient = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { data, error } = await tempClient.auth.signInWithPassword({
email: SUPABASE_EMAIL,
password: SUPABASE_PASSWORD,
});
if (error || !data.session) {
throw new Error(`Auth failed: ${error?.message}`);
}
return {
access_token: data.session.access_token,
refresh_token: data.session.refresh_token,
user: data.session.user,
};
}
// supabase を access_token で再作成
async function createAuthedClient() {
const tokenInfo = await getFreshToken();
// 必要なら .env 書き換え保存などもここで
return createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
global: {
headers: {
Authorization: `Bearer ${tokenInfo.access_token}`,
},
},
});
}
// アクセス時の共通関数ラッパー(エラー時リトライ)
async function withAuthRetry<T>(fn: () => Promise<T>): Promise<T> {
let result: T;
try {
result = await fn();
} catch (err) {
const message = (err as Error).message;
if (message.includes("JWT expired") || message.includes("401")) {
console.warn("🔁 トークン期限切れのため再認証中...");
supabase = await createAuthedClient();
result = await fn(); // retry
} else {
throw err;
}
}
return result;
}
// グローバル Supabase クライアント(認証付き)
let supabase = await createAuthedClient();
const { data: { user } } = await supabase.auth.getUser();
// 実行
await uploadArticles();
withAuthRetry
関数でトークン切れの時は再度取得し直す。
async function uploadCategory(category: string) {
await withAuthRetry(async () => {
const { error } = await supabase
.from("category")
.upsert([{ name: category, user_id: user?.id }], { onConflict: "name" });
if (error) {
console.error(`Error uploading category ${category}:`, error.message);
} else {
console.log(`Uploaded category: ${category}`);
}
});
}
閲覧
閲覧は誰でも可能にする
-- 誰でも(匿名含む)読める公開記事
create policy "Allow read public articles"
on articles
for select
using (
private = false
);
-- 誰でも読めるカテゴリ
create policy "Allow read categories"
on category
for select
using (true);
-- 非ログインユーザーでも CDN 経由で画像取得OK
create policy "Allow public read of blog-images"
on storage.objects
for select
using (
bucket_id = 'blog-images'
);
認証処理は追加しなくておk
おまけでこれもやっておいた。

公開日
更新日