[{"data":1,"prerenderedAt":498},["ShallowReactive",2],{"blog-guides/supabase":3},{"id":4,"title":5,"body":6,"category":478,"date":479,"dateModified":479,"description":480,"draft":481,"extension":482,"faq":483,"featured":481,"headerVariant":484,"image":483,"keywords":483,"meta":485,"navigation":486,"ogDescription":487,"ogTitle":483,"path":488,"readTime":489,"schemaOrg":490,"schemaType":491,"seo":492,"sitemap":493,"stem":494,"tags":495,"twitterCard":496,"__hash__":497},"blog/blog/guides/supabase.md","Supabase Security Guide: Row Level Security and Best Practices",{"type":7,"value":8,"toc":452},"minimark",[9,16,21,24,53,62,66,119,128,132,135,140,155,159,162,171,175,189,198,202,206,215,219,228,232,241,245,254,258,261,270,275,278,281,284,287,290,293,297,301,310,314,323,332,336,345,349,358,362,366,369,372,375,378,381,384,387,390,393,421,440],[10,11,12],"tldr",{},[13,14,15],"p",{},"Supabase exposes your database directly to the frontend, which means Row Level Security (RLS) is essential. Without RLS, anyone with your anon key can read and write all your data. Enable RLS on every table, write policies that check user identity, and never expose your service role key to the browser. The anon key is public, so your security comes from RLS policies.",[17,18,20],"h2",{"id":19},"understanding-supabases-security-model","Understanding Supabase's Security Model",[13,22,23],{},"Supabase gives your frontend direct database access through its JavaScript client. This is powerful but requires understanding how security works:",[25,26,27,35,41,47],"ul",{},[28,29,30,34],"li",{},[31,32,33],"strong",{},"Anon key:"," Public key that can be in your frontend code",[28,36,37,40],{},[31,38,39],{},"Service role key:"," Private key that bypasses RLS (server-side only)",[28,42,43,46],{},[31,44,45],{},"Row Level Security:"," Database policies that control access",[28,48,49,52],{},[31,50,51],{},"Auth:"," User authentication that policies can reference",[54,55,56],"danger-box",{},[13,57,58,61],{},[31,59,60],{},"Critical:"," Without RLS enabled, anyone with your anon key (which is public) can read, modify, or delete all data in your tables. This is the #1 security mistake we see in Supabase projects.",[17,63,65],{"id":64},"the-two-keys-you-need-to-know","The Two Keys You Need to Know",[67,68,69,88],"table",{},[70,71,72],"thead",{},[73,74,75,79,82,85],"tr",{},[76,77,78],"th",{},"Key Type",[76,80,81],{},"Can Be Public?",[76,83,84],{},"Respects RLS?",[76,86,87],{},"Use Case",[89,90,91,105],"tbody",{},[73,92,93,97,100,102],{},[94,95,96],"td",{},"anon key",[94,98,99],{},"Yes",[94,101,99],{},[94,103,104],{},"Frontend/client-side",[73,106,107,110,113,116],{},[94,108,109],{},"service_role key",[94,111,112],{},"No, never",[94,114,115],{},"No (bypasses)",[94,117,118],{},"Server-side only",[120,121,122],"warning-box",{},[13,123,124,127],{},[31,125,126],{},"Never expose your service role key."," If it's in your frontend code, environment variables visible to the browser, or a public repository, attackers can bypass all your security policies.",[17,129,131],{"id":130},"row-level-security-rls-explained","Row Level Security (RLS) Explained",[13,133,134],{},"RLS lets you define rules at the database level that control who can access which rows. Think of it as a filter that runs on every query.",[136,137,139],"h3",{"id":138},"step-1-enable-rls-on-your-tables","Step 1: Enable RLS on Your Tables",[141,142,144],"code-block",{"label":143},"Enable RLS (SQL)",[145,146,151],"pre",{"className":147,"code":149,"language":150},[148],"language-text","-- Enable RLS on a table\nALTER TABLE posts ENABLE ROW LEVEL SECURITY;\n\n-- Important: RLS is OFF by default on new tables\n-- You must enable it explicitly for each table\n","text",[152,153,149],"code",{"__ignoreMap":154},"",[136,156,158],{"id":157},"step-2-create-policies","Step 2: Create Policies",[13,160,161],{},"Without policies, RLS blocks all access. You need to create policies that define who can do what:",[141,163,165],{"label":164},"Basic RLS policies for a posts table",[145,166,169],{"className":167,"code":168,"language":150},[148],"-- Anyone can read published posts\nCREATE POLICY \"Public posts are viewable by everyone\"\nON posts FOR SELECT\nUSING (published = true);\n\n-- Users can only insert their own posts\nCREATE POLICY \"Users can create their own posts\"\nON posts FOR INSERT\nWITH CHECK (auth.uid() = user_id);\n\n-- Users can only update their own posts\nCREATE POLICY \"Users can update their own posts\"\nON posts FOR UPDATE\nUSING (auth.uid() = user_id)\nWITH CHECK (auth.uid() = user_id);\n\n-- Users can only delete their own posts\nCREATE POLICY \"Users can delete their own posts\"\nON posts FOR DELETE\nUSING (auth.uid() = user_id);\n",[152,170,168],{"__ignoreMap":154},[136,172,174],{"id":173},"understanding-using-vs-with-check","Understanding USING vs WITH CHECK",[25,176,177,183],{},[28,178,179,182],{},[31,180,181],{},"USING:"," Filters which rows can be seen/affected (for SELECT, UPDATE, DELETE)",[28,184,185,188],{},[31,186,187],{},"WITH CHECK:"," Validates new data (for INSERT and UPDATE)",[141,190,192],{"label":191},"Example: Prevent users from changing ownership",[145,193,196],{"className":194,"code":195,"language":150},[148],"-- This policy allows updates but prevents changing user_id\nCREATE POLICY \"Users can update own posts, not ownership\"\nON posts FOR UPDATE\nUSING (auth.uid() = user_id)        -- Can only see own posts\nWITH CHECK (auth.uid() = user_id);  -- Can't change user_id to someone else\n",[152,197,195],{"__ignoreMap":154},[17,199,201],{"id":200},"common-rls-patterns","Common RLS Patterns",[136,203,205],{"id":204},"pattern-1-user-owned-data","Pattern 1: User-Owned Data",[141,207,209],{"label":208},"Users can only access their own data",[145,210,213],{"className":211,"code":212,"language":150},[148],"-- For a table with a user_id column\nCREATE POLICY \"Users access own data\"\nON user_settings FOR ALL\nUSING (auth.uid() = user_id)\nWITH CHECK (auth.uid() = user_id);\n",[152,214,212],{"__ignoreMap":154},[136,216,218],{"id":217},"pattern-2-organizationteam-access","Pattern 2: Organization/Team Access",[141,220,222],{"label":221},"Team members can access team data",[145,223,226],{"className":224,"code":225,"language":150},[148],"-- Check if user belongs to the organization\nCREATE POLICY \"Team members access team data\"\nON projects FOR ALL\nUSING (\n  EXISTS (\n    SELECT 1 FROM team_members\n    WHERE team_members.org_id = projects.org_id\n    AND team_members.user_id = auth.uid()\n  )\n);\n",[152,227,225],{"__ignoreMap":154},[136,229,231],{"id":230},"pattern-3-public-read-authenticated-write","Pattern 3: Public Read, Authenticated Write",[141,233,235],{"label":234},"Public content with authenticated authors",[145,236,239],{"className":237,"code":238,"language":150},[148],"-- Anyone can read\nCREATE POLICY \"Public read\" ON articles\nFOR SELECT USING (true);\n\n-- Only authenticated users can write\nCREATE POLICY \"Authenticated insert\" ON articles\nFOR INSERT WITH CHECK (auth.uid() IS NOT NULL);\n\n-- Authors can edit their own\nCREATE POLICY \"Authors can update\" ON articles\nFOR UPDATE USING (auth.uid() = author_id);\n",[152,240,238],{"__ignoreMap":154},[136,242,244],{"id":243},"pattern-4-role-based-access","Pattern 4: Role-Based Access",[141,246,248],{"label":247},"Admin-only access",[145,249,252],{"className":250,"code":251,"language":150},[148],"-- Create a function to check admin status\nCREATE OR REPLACE FUNCTION is_admin()\nRETURNS BOOLEAN AS $$\nBEGIN\n  RETURN EXISTS (\n    SELECT 1 FROM user_roles\n    WHERE user_id = auth.uid()\n    AND role = 'admin'\n  );\nEND;\n$$ LANGUAGE plpgsql SECURITY DEFINER;\n\n-- Use in policy\nCREATE POLICY \"Admins can do anything\" ON sensitive_data\nFOR ALL USING (is_admin());\n",[152,253,251],{"__ignoreMap":154},[17,255,257],{"id":256},"testing-your-rls-policies","Testing Your RLS Policies",[13,259,260],{},"Always test policies before deploying. Supabase provides tools to help:",[141,262,264],{"label":263},"Test as a specific user in SQL Editor",[145,265,268],{"className":266,"code":267,"language":150},[148],"-- Temporarily act as a specific user\nSET LOCAL ROLE authenticated;\nSET LOCAL request.jwt.claims = '{\"sub\": \"user-uuid-here\"}';\n\n-- Now run queries to test your policies\nSELECT * FROM posts;  -- Should only show allowed rows\n\n-- Reset\nRESET ROLE;\n",[152,269,267],{"__ignoreMap":154},[271,272,274],"h4",{"id":273},"rls-testing-checklist","RLS Testing Checklist",[13,276,277],{},"Test as unauthenticated user (should see only public data)",[13,279,280],{},"Test as authenticated user (should see own data)",[13,282,283],{},"Test accessing another user's data (should be blocked)",[13,285,286],{},"Test INSERT with wrong user_id (should fail)",[13,288,289],{},"Test UPDATE to change ownership (should fail)",[13,291,292],{},"Test DELETE on another user's data (should fail)",[17,294,296],{"id":295},"common-mistakes-to-avoid","Common Mistakes to Avoid",[136,298,300],{"id":299},"mistake-1-forgetting-to-enable-rls","Mistake 1: Forgetting to Enable RLS",[141,302,304],{"label":303},"Check which tables have RLS enabled",[145,305,308],{"className":306,"code":307,"language":150},[148],"-- List all tables and their RLS status\nSELECT tablename, rowsecurity\nFROM pg_tables\nWHERE schemaname = 'public';\n\n-- If rowsecurity is 'f' (false), RLS is not enabled!\n",[152,309,307],{"__ignoreMap":154},[136,311,313],{"id":312},"mistake-2-using-service-role-key-in-frontend","Mistake 2: Using Service Role Key in Frontend",[141,315,317],{"label":316},"Wrong: Service key in browser",[145,318,321],{"className":319,"code":320,"language":150},[148],"// NEVER DO THIS\nconst supabase = createClient(\n  'https://xxx.supabase.co',\n  'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...'  // Service role key!\n);\n",[152,322,320],{"__ignoreMap":154},[141,324,326],{"label":325},"Correct: Anon key in browser",[145,327,330],{"className":328,"code":329,"language":150},[148],"// Use anon key in frontend\nconst supabase = createClient(\n  process.env.NEXT_PUBLIC_SUPABASE_URL,\n  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY  // Anon key is safe\n);\n",[152,331,329],{"__ignoreMap":154},[136,333,335],{"id":334},"mistake-3-overly-permissive-policies","Mistake 3: Overly Permissive Policies",[141,337,339],{"label":338},"Dangerous: Allow all",[145,340,343],{"className":341,"code":342,"language":150},[148],"-- DON'T DO THIS\nCREATE POLICY \"Allow everything\" ON users\nFOR ALL USING (true) WITH CHECK (true);\n-- This defeats the purpose of RLS!\n",[152,344,342],{"__ignoreMap":154},[136,346,348],{"id":347},"mistake-4-not-handling-null-user_id","Mistake 4: Not Handling NULL user_id",[141,350,352],{"label":351},"Handle NULL cases explicitly",[145,353,356],{"className":354,"code":355,"language":150},[148],"-- Be explicit about NULL handling\nCREATE POLICY \"Users access own data\"\nON profiles FOR ALL\nUSING (\n  auth.uid() IS NOT NULL  -- Must be logged in\n  AND auth.uid() = user_id\n);\n",[152,357,355],{"__ignoreMap":154},[17,359,361],{"id":360},"supabase-security-checklist","Supabase Security Checklist",[271,363,365],{"id":364},"before-going-to-production","Before Going to Production",[13,367,368],{},"RLS enabled on ALL tables with data",[13,370,371],{},"Every table has appropriate policies",[13,373,374],{},"Service role key is NOT in frontend code",[13,376,377],{},"Service role key is NOT in public repository",[13,379,380],{},"Tested policies as different user types",[13,382,383],{},"No overly permissive policies (USING true)",[13,385,386],{},"Storage buckets have appropriate policies",[13,388,389],{},"Edge Functions use proper authentication",[13,391,392],{},"Database password is strong and not shared",[394,395,396,403,409,415],"faq-section",{},[397,398,400],"faq-item",{"question":399},"Is the anon key really safe to expose?",[13,401,402],{},"Yes, the anon key is designed to be public. It only allows access that your RLS policies permit. Think of it like a locked door where everyone has a key, but the lock only opens for authorized people based on who they are. Your security comes from RLS policies, not from hiding the anon key.",[397,404,406],{"question":405},"What if I accidentally exposed my service role key?",[13,407,408],{},"Rotate it immediately in Supabase Dashboard under Settings > API. Then update your server-side code with the new key. Also audit your data for any unauthorized changes that might have occurred.",[397,410,412],{"question":411},"Do I need RLS if I only use server-side code?",[13,413,414],{},"If you only access Supabase from server-side code using the service role key, RLS isn't strictly necessary for security. However, it's still good practice because it provides defense in depth and protects against accidental frontend exposure.",[397,416,418],{"question":417},"How do RLS policies affect performance?",[13,419,420],{},"RLS policies run on every query, so complex policies can impact performance. Keep policies simple, use indexes on columns referenced in policies, and consider using security definer functions for complex logic that can be optimized.",[422,423,424,430,435],"related-articles",{},[425,426],"related-card",{"description":427,"href":428,"title":429},"Step-by-step tutorial","/blog/how-to/setup-supabase-rls","How to Set Up Supabase RLS",[425,431],{"description":432,"href":433,"title":434},"What can go wrong","/blog/vulnerabilities/broken-access-control","Missing RLS Vulnerability",[425,436],{"description":437,"href":438,"title":439},"Compare security models","/blog/comparisons/supabase-vs-firebase","Supabase vs Firebase Security",[441,442,445,449],"cta-box",{"href":443,"label":444},"/","Start Free Scan",[17,446,448],{"id":447},"check-your-supabase-security","Check Your Supabase Security",[13,450,451],{},"Scan your project for missing RLS policies and exposed keys.",{"title":154,"searchDepth":453,"depth":453,"links":454},2,[455,456,457,463,469,470,476,477],{"id":19,"depth":453,"text":20},{"id":64,"depth":453,"text":65},{"id":130,"depth":453,"text":131,"children":458},[459,461,462],{"id":138,"depth":460,"text":139},3,{"id":157,"depth":460,"text":158},{"id":173,"depth":460,"text":174},{"id":200,"depth":453,"text":201,"children":464},[465,466,467,468],{"id":204,"depth":460,"text":205},{"id":217,"depth":460,"text":218},{"id":230,"depth":460,"text":231},{"id":243,"depth":460,"text":244},{"id":256,"depth":453,"text":257},{"id":295,"depth":453,"text":296,"children":471},[472,473,474,475],{"id":299,"depth":460,"text":300},{"id":312,"depth":460,"text":313},{"id":334,"depth":460,"text":335},{"id":347,"depth":460,"text":348},{"id":360,"depth":453,"text":361},{"id":447,"depth":453,"text":448},"guides","2026-01-30","Complete security guide for Supabase. Master Row Level Security (RLS), protect your API keys, and secure your database for production.",false,"md",null,"blue",{},true,"Learn to properly secure your Supabase database with RLS policies and key management.","/blog/guides/supabase","12 min read","[object Object]","Article",{"title":5,"description":480},{"loc":488},"blog/guides/supabase",[],"summary_large_image","IqEEm0f6F_Pr-hT6-QCST47nvjKt8pUpPfbwH5eLTP0",1775843918547]