<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Ali Zaib</title>
    <description>The latest articles on DEV Community by Ali Zaib (@ali_zaib_randhawa).</description>
    <link>https://dev.to/ali_zaib_randhawa</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3742288%2Ff6777aa1-80ef-49d1-82e9-4c10e2cdf38a.png</url>
      <title>DEV Community: Ali Zaib</title>
      <link>https://dev.to/ali_zaib_randhawa</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ali_zaib_randhawa"/>
    <language>en</language>
    <item>
      <title>I Spent Three Weeks Reverse Engineering a Mobile App’s API — Here’s What It Took</title>
      <dc:creator>Ali Zaib</dc:creator>
      <pubDate>Sat, 20 Jun 2026 09:05:56 +0000</pubDate>
      <link>https://dev.to/ali_zaib_randhawa/i-spent-three-weeks-reverse-engineering-a-mobile-apps-api-heres-what-it-took-2797</link>
      <guid>https://dev.to/ali_zaib_randhawa/i-spent-three-weeks-reverse-engineering-a-mobile-apps-api-heres-what-it-took-2797</guid>
      <description>&lt;p&gt;A while back, a client asked me a deceptively simple question: &lt;em&gt;"Can you find out how our competitor's mobile app talks to its backend?"&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;No public API docs. No Postman collection floating around GitHub. Just a Flutter app, a few permissions in the manifest, and a backend that clearly wasn't expecting anyone to come knocking.&lt;/p&gt;

&lt;p&gt;I said yes before I fully understood what I was signing up for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The First Wall: Flutter Doesn't Decompile Like a Normal App
&lt;/h2&gt;

&lt;p&gt;If you've reverse engineered a native Android app before, you know the drill — pull the APK, run it through a decompiler, read mostly-readable Java or Kotlin, find the API calls, done in an afternoon.&lt;/p&gt;

&lt;p&gt;Flutter apps don't play by those rules. The business logic isn't sitting in readable bytecode — it's compiled down to a Dart AOT (ahead-of-time) snapshot, a chunk of near-opaque machine code that standard decompilers just shrug at. Tools like jadx show you the Android shell around the app, but the actual logic — the part that mattered — was somewhere else entirely, encoded in a format almost nothing in the public tooling ecosystem handles well.&lt;/p&gt;

&lt;p&gt;That was the first sign this wasn't going to be an afternoon job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Going Lower: Snapshot Parsing and Runtime Hooking
&lt;/h2&gt;

&lt;p&gt;I ended up going two layers deeper than I'd planned.&lt;/p&gt;

&lt;p&gt;First, static analysis of the Dart snapshot itself — figuring out how Flutter lays out compiled functions and objects in memory, well enough to start identifying &lt;em&gt;where&lt;/em&gt; the authentication logic actually lived inside this blob of machine code.&lt;/p&gt;

&lt;p&gt;Second, once static analysis hit its ceiling, I moved to dynamic instrumentation — attaching to the running app and hooking functions &lt;em&gt;as they executed&lt;/em&gt;, watching real values flow through real function calls instead of guessing from disassembly. This is where things got genuinely interesting: confirming hypotheses by intercepting live execution rather than reading static bytes is a completely different skill than traditional reverse engineering, and most of the public writeups on Flutter analysis stop well before this point.&lt;/p&gt;

&lt;p&gt;There was also a side quest I didn't expect: the app pinned its own SSL implementation in a way that defeated the usual bypass tools outright. The standard playbook (objection, frida-ssl-pinning-bypass) just didn't work — which meant going up a layer, hooking application logic instead of the network layer, and finding a different way to observe what was actually being sent.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Wall That Took the Longest
&lt;/h2&gt;

&lt;p&gt;Eventually I could see the request format. I could see &lt;em&gt;that&lt;/em&gt; something was signing each request cryptographically before it left the device. What I didn't have yet was the exact signing behavior — and getting it wrong didn't throw a helpful error. It just failed, silently, the same way, no matter what I changed.&lt;/p&gt;

&lt;p&gt;I lost more time to that single wall than to everything else in the project combined. The eventual breakthrough came down to one detail buried in how the signing input was constructed — small enough that I'd walked past it a dozen times without seeing it.&lt;/p&gt;

&lt;p&gt;I won't go into the specific fix here — partly because it's the most sensitive part of the work, and partly because it's genuinely the best part of the writeup, and it deserves more than a paragraph.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where the Full Breakdown Lives
&lt;/h2&gt;

&lt;p&gt;This post is the story. The &lt;strong&gt;full technical writeup&lt;/strong&gt; — the Dart snapshot parsing approach, the actual Frida hooking strategy, how I worked around the SSL pinning, and the exact detail that broke the signing wall open — is published on IQCrafter:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.iqcrafter.com/blog/flutter-app-reverse-engineering-security-research" rel="noopener noreferrer"&gt;Read the full technical breakdown →&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If you've fought with Flutter reverse engineering, Dart AOT snapshots, or signed mobile APIs before, I think you'll find it useful — and if you're dealing with something similar right now, that's also exactly the kind of problem my team and I take on.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;I'm Ali, a software engineer working on backend systems, mobile security research, and the occasional deep technical rabbit hole. I write about the same kind of work at &lt;a href="https://www.iqcrafter.com" rel="noopener noreferrer"&gt;IQCrafter&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>security</category>
      <category>mobile</category>
      <category>flutter</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>Stop Uploading Your CSV Files to Random Tools — Use This Instead</title>
      <dc:creator>Ali Zaib</dc:creator>
      <pubDate>Thu, 26 Mar 2026 19:47:23 +0000</pubDate>
      <link>https://dev.to/ali_zaib_randhawa/stop-uploading-your-csv-files-to-random-tools-use-this-instead-2cbo</link>
      <guid>https://dev.to/ali_zaib_randhawa/stop-uploading-your-csv-files-to-random-tools-use-this-instead-2cbo</guid>
      <description>&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63134rk7fwhgadf5fs23.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F63134rk7fwhgadf5fs23.png" alt="CSV toolkit"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Every backend engineer has hit this at least once.&lt;/p&gt;

&lt;p&gt;You export a CSV from the database — users, orders, transactions — &lt;br&gt;
and something is wrong. The import fails. Columns are misaligned. &lt;br&gt;
Excel mangled the encoding. Half the dates are in the wrong format &lt;br&gt;
because the file came from a European locale.&lt;/p&gt;

&lt;p&gt;So you do what feels natural: paste it into ChatGPT, or upload it &lt;br&gt;
to some random online converter, just to &lt;em&gt;look&lt;/em&gt; at the structure.&lt;/p&gt;

&lt;p&gt;And somewhere in the back of your head, something feels off about &lt;br&gt;
that. Because it should.&lt;/p&gt;


&lt;h2&gt;
  
  
  I built a browser-based CSV Toolkit to fix this
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/M4GqPms3ehU"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.toolshubkit.com/tool/csv-toolkit" rel="noopener noreferrer"&gt;CSV Toolkit on ToolsHubKit&lt;/a&gt;&lt;/strong&gt; &lt;br&gt;
— free, no signup, runs entirely in your browser. The file never &lt;br&gt;
leaves your machine. You can disconnect from the internet and it &lt;br&gt;
still works.&lt;/p&gt;




&lt;h2&gt;
  
  
  Why CSV is actually hard
&lt;/h2&gt;

&lt;p&gt;CSV &lt;em&gt;looks&lt;/em&gt; simple. Values separated by commas, rows by newlines.&lt;/p&gt;

&lt;p&gt;Except there's no real standard. RFC 4180 is advisory, and most &lt;br&gt;
exporters partially ignore it. Here's what actually breaks in &lt;br&gt;
production:&lt;/p&gt;

&lt;h3&gt;
  
  
  Delimiter confusion
&lt;/h3&gt;

&lt;p&gt;Not all CSVs use commas. European Excel uses semicolons (because &lt;br&gt;
the comma is the decimal separator there). MySQL dumps use tabs. &lt;br&gt;
Legacy systems use pipes. Assume the wrong delimiter and every &lt;br&gt;
column is misaligned — and the error is often silent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Encoding mismatches
&lt;/h3&gt;

&lt;p&gt;Excel on Windows exports Windows-1252 or Latin-1 by default. &lt;br&gt;
Google Sheets exports UTF-8. MySQL defaults to Latin-1. Open a &lt;br&gt;
Latin-1 file in a UTF-8 parser and &lt;code&gt;é&lt;/code&gt; becomes &lt;code&gt;Ã©&lt;/code&gt;. Fun to &lt;br&gt;
debug at 2am.&lt;/p&gt;

&lt;h3&gt;
  
  
  The BOM problem
&lt;/h3&gt;

&lt;p&gt;Excel prepends a UTF-8 Byte Order Mark (&lt;code&gt;EF BB BF&lt;/code&gt;) to CSV &lt;br&gt;
exports. When parsers don't handle it, your first column header &lt;br&gt;
gets an invisible character prefix. The toolkit strips BOMs &lt;br&gt;
automatically.&lt;/p&gt;

&lt;h3&gt;
  
  
  Inconsistent column counts
&lt;/h3&gt;

&lt;p&gt;Row 47 has 8 fields. Every other row has 7. The import fails at &lt;br&gt;
row 47 — or worse, silently shifts all data right for the rest &lt;br&gt;
of the file.&lt;/p&gt;

&lt;h3&gt;
  
  
  Quoted field edge cases
&lt;/h3&gt;

&lt;p&gt;Per RFC 4180, any field containing a comma must be quoted. Many &lt;br&gt;
generators skip this. So &lt;code&gt;Acme Corp, Inc&lt;/code&gt; becomes two separate &lt;br&gt;
fields in your import target.&lt;/p&gt;




&lt;h2&gt;
  
  
  What the toolkit does
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Auto-detects delimiter&lt;/strong&gt; — comma, semicolon, tab, pipe&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Handles encoding&lt;/strong&gt; — UTF-8, Latin-1, Windows-1252, BOM stripping&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Instant table preview&lt;/strong&gt; — see exactly what a parser sees&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Highlights malformed rows&lt;/strong&gt; — inconsistent column counts, 
unbalanced quotes, escaped field issues&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Filters in-browser&lt;/strong&gt; — keep rows where &lt;code&gt;status = "active"&lt;/code&gt;, 
drop PII columns, reorder to match import target schema&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Multi-format export&lt;/strong&gt; — clean CSV, JSON array, Markdown table&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Works on large files&lt;/strong&gt; — 100k+ rows, all local&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  The import workflow that saves hours
&lt;/h2&gt;

&lt;p&gt;Before any significant data import:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;1. Preview first&lt;/strong&gt;&lt;br&gt;
Drop the file in and scan the first 50–100 rows. Catch structural &lt;br&gt;
issues before they affect the full dataset.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Validate column counts&lt;/strong&gt;&lt;br&gt;
Malformed rows are highlighted immediately. Fix them before the &lt;br&gt;
import, not after it fails at row 50,000.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Handle PII&lt;/strong&gt;&lt;br&gt;
Before sharing any CSV in a ticket or Slack thread — remove &lt;br&gt;
sensitive columns in the toolkit, or run it through the &lt;br&gt;
&lt;a href="https://www.toolshubkit.com/tool/pii-redactor" rel="noopener noreferrer"&gt;PII Redactor&lt;/a&gt; &lt;br&gt;
for full redaction.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;4. Export to JSON if needed&lt;/strong&gt;&lt;br&gt;
For API testing or JavaScript pipelines, export directly to JSON &lt;br&gt;
array. Validate the structure with the &lt;br&gt;
&lt;a href="https://www.toolshubkit.com/tool/json-formatter" rel="noopener noreferrer"&gt;JSON Formatter&lt;/a&gt; &lt;br&gt;
before submitting.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;5. Import the cleaned version&lt;/strong&gt;&lt;br&gt;
Not the raw export. Always the cleaned version.&lt;/p&gt;

&lt;p&gt;A 30-second preview step consistently catches the issues that &lt;br&gt;
would otherwise fail halfway through importing 500,000 rows and &lt;br&gt;
leave the database in a partial state.&lt;/p&gt;




&lt;h2&gt;
  
  
  The privacy argument
&lt;/h2&gt;

&lt;p&gt;Most of us have a vague sense that uploading sensitive data to &lt;br&gt;
random web tools is bad. Let's be specific:&lt;/p&gt;

&lt;p&gt;Database exports contain PII almost by default — names, emails, &lt;br&gt;
phone numbers, addresses, payment references. When you upload &lt;br&gt;
that file to an online tool, you have no visibility into:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Where the file goes&lt;/li&gt;
&lt;li&gt;How long it's stored&lt;/li&gt;
&lt;li&gt;Whether it's logged&lt;/li&gt;
&lt;li&gt;Whether it ends up in a training dataset&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The CSV Toolkit's client-side architecture isn't a marketing claim &lt;br&gt;
— it's a technical constraint. There's no server to upload to. &lt;br&gt;
The parsing runs in your browser's JavaScript engine, on your &lt;br&gt;
hardware, against your local memory.&lt;/p&gt;

&lt;p&gt;Safe for: production database exports, customer data samples, &lt;br&gt;
internal financial reports — anything you'd think twice about &lt;br&gt;
emailing.&lt;/p&gt;




&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://www.toolshubkit.com/tool/csv-toolkit" rel="noopener noreferrer"&gt;→ CSV Toolkit on ToolsHubKit&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Free. No account. No upload. Works offline.&lt;/p&gt;

&lt;p&gt;ToolsHubKit has 40+ developer utilities built the same way — &lt;br&gt;
all browser-based, all privacy-first. If you find this useful, &lt;br&gt;
the suite is worth bookmarking.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Built this as a side project. Feedback welcome in the comments &lt;br&gt;
— especially edge cases the parser should handle that it &lt;br&gt;
currently doesn't.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>devtools</category>
      <category>csv</category>
      <category>opensource</category>
    </item>
    <item>
      <title>Securing Next.js + Supabase After Switching to NextAuth</title>
      <dc:creator>Ali Zaib</dc:creator>
      <pubDate>Sun, 08 Feb 2026 13:36:34 +0000</pubDate>
      <link>https://dev.to/ali_zaib_randhawa/securing-nextjs-supabase-after-switching-to-nextauth-22il</link>
      <guid>https://dev.to/ali_zaib_randhawa/securing-nextjs-supabase-after-switching-to-nextauth-22il</guid>
      <description>&lt;h2&gt;
  
  
  Securing Next.js + Supabase After Switching to NextAuth
&lt;/h2&gt;

&lt;h2&gt;
  
  
  The Problem
&lt;/h2&gt;

&lt;p&gt;I was using Supabase Auth with Google OAuth, but users saw "supabase.co" on the consent screen instead of my app's branding. Custom domains require a paid Supabase plan, so I switched to NextAuth.js.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But here's the catch:&lt;/strong&gt; Removing Supabase Auth also removed Row Level Security (RLS), which created major security vulnerabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Security Risk
&lt;/h2&gt;

&lt;p&gt;Without proper implementation, your Supabase database becomes wide open:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Anyone can grab your keys from the browser and do this:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;YOUR_URL&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;EXPOSED_ANON_KEY&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;users&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Read everything&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;orders&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="c1"&gt;// Delete everything&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Why?&lt;/strong&gt; Because:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your Supabase keys are exposed in the frontend bundle&lt;/li&gt;
&lt;li&gt;No RLS means no access control&lt;/li&gt;
&lt;li&gt;Client-side checks can be bypassed&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The Secure Solution
&lt;/h2&gt;

&lt;p&gt;All database operations must go through Next.js API routes with proper session validation.&lt;/p&gt;

&lt;h3&gt;
  
  
  Architecture
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Browser → Next.js API Routes → Supabase
            ↓
      Session Check
      Authorization
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 1: Environment Variables
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;# .env.local (server-side only)&lt;/span&gt;
&lt;span class="nv"&gt;NEXTAUTH_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://yourdomain.com
&lt;span class="nv"&gt;NEXTAUTH_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-secret
&lt;span class="nv"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-google-id
&lt;span class="nv"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-google-secret

&lt;span class="c"&gt;# Use SERVICE_ROLE key, not anon key!&lt;/span&gt;
&lt;span class="nv"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;https://your-project.supabase.co
&lt;span class="nv"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;your-service-role-key
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 2: NextAuth Configuration
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/auth/[...nextauth].js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;NextAuth&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;GoogleProvider&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/providers/google&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="na"&gt;providers&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nc"&gt;GoogleProvider&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;clientId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;clientSecret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GOOGLE_CLIENT_SECRET&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}),&lt;/span&gt;
  &lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="na"&gt;callbacks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;session&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sub&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;
    &lt;span class="p"&gt;},&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nc"&gt;NextAuth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 3: Secure API Routes
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/get-data.js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getServerSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./auth/[...nextauth]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// 1. Verify authentication&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// 2. Create Supabase client (server-side only)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt; &lt;span class="c1"&gt;// Never expose this!&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="c1"&gt;// 3. Query with proper authorization&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Critical: filter by user&lt;/span&gt;

  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Step 4: Client-Side Calls
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ NEVER do this in client components&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;url&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Exposes keys!&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ ALWAYS call your API routes instead&lt;/span&gt;
&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;fetchData&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;fetch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/get-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Complete CRUD Example
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// pages/api/items/[id].js&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;getServerSession&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;next-auth/next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;../auth/[...nextauth]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@supabase/supabase-js&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_URL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;
  &lt;span class="p"&gt;)&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;query&lt;/span&gt;

  &lt;span class="c1"&gt;// GET&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;GET&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;single&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;404&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Not found&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// PUT&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;PUT&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;update&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;description&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Verify ownership&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="c1"&gt;// DELETE&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;method&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;DELETE&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;delete&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Verify ownership&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;500&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;message&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;success&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;405&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Method not allowed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Security Checklist
&lt;/h2&gt;

&lt;p&gt;Before deploying, verify:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;[ ] No Supabase imports in client-side code&lt;/li&gt;
&lt;li&gt;[ ] All data operations through API routes&lt;/li&gt;
&lt;li&gt;[ ] Every API route validates session&lt;/li&gt;
&lt;li&gt;[ ] All queries filter by &lt;code&gt;session.user.id&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;[ ] Service role key only in server environment variables&lt;/li&gt;
&lt;li&gt;[ ] Open DevTools → No requests to &lt;code&gt;*.supabase.co&lt;/code&gt; from browser&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Quick Test
&lt;/h2&gt;

&lt;p&gt;Open your browser DevTools:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to Network tab&lt;/li&gt;
&lt;li&gt;Perform data operations&lt;/li&gt;
&lt;li&gt;If you see requests to &lt;code&gt;*.supabase.co&lt;/code&gt; from the browser, you have a problem!&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All requests should go to your &lt;code&gt;/api/*&lt;/code&gt; routes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Mistakes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Forgetting to Filter by User ID
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Returns ALL users' data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Returns only current user's data&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;eq&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;user_id&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  2. Using Anon Key Instead of Service Role
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ Limited permissions&lt;/span&gt;
&lt;span class="nx"&gt;SUPABASE_ANON_KEY&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Full access (server-side only!)&lt;/span&gt;
&lt;span class="nx"&gt;SUPABASE_SERVICE_ROLE_KEY&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Skipping Session Check
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// ❌ No authentication&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;supabase&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;items&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;select&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// ✅ Always verify session&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handler&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;session&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;getServerSession&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;authOptions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;session&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;status&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;401&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;json&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;error&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unauthorized&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt;
  &lt;span class="c1"&gt;// ... rest of code&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Additional Security Layers
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Rate Limiting
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;rateLimit&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express-rate-limit&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;limiter&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;rateLimit&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;windowMs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;15&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;60&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="c1"&gt;// 15 minutes&lt;/span&gt;
  &lt;span class="na"&gt;max&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="c1"&gt;// limit each IP to 100 requests per window&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Input Validation
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;zod&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;object&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;min&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
  &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;z&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;string&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;max&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1000&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt;

&lt;span class="c1"&gt;// In your API route:&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validated&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;schema&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;body&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Performance Tips
&lt;/h2&gt;

&lt;p&gt;The API layer adds ~50ms latency, but you can optimize:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Use SWR for caching:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;useSWR&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;swr&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;Component&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;data&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useSWR&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/api/get-data&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fetcher&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="nx"&gt;div&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="sr"&gt;/div&lt;/span&gt;&lt;span class="err"&gt;&amp;gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Add database indexes:&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight sql"&gt;&lt;code&gt;&lt;span class="k"&gt;CREATE&lt;/span&gt; &lt;span class="k"&gt;INDEX&lt;/span&gt; &lt;span class="n"&gt;idx_items_user_id&lt;/span&gt; &lt;span class="k"&gt;ON&lt;/span&gt; &lt;span class="n"&gt;items&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  The Tradeoff
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;What you gain:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ Custom branding on OAuth&lt;/li&gt;
&lt;li&gt;✅ Full control over auth flow&lt;/li&gt;
&lt;li&gt;✅ No vendor lock-in&lt;/li&gt;
&lt;li&gt;✅ Actual security&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;What you lose:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;❌ Automatic RLS convenience&lt;/li&gt;
&lt;li&gt;❌ Built-in Supabase session management&lt;/li&gt;
&lt;li&gt;❌ Direct client-to-DB queries&lt;/li&gt;
&lt;li&gt;❌ Some Supabase auth features&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  When to Use This Approach
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Good for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Early-stage products where branding matters&lt;/li&gt;
&lt;li&gt;Apps on tight budgets&lt;/li&gt;
&lt;li&gt;Teams that want full auth control&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Not ideal for:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enterprise apps heavily using Supabase auth features&lt;/li&gt;
&lt;li&gt;Teams that prefer less backend code&lt;/li&gt;
&lt;li&gt;Projects where Supabase Pro is already affordable&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Alternatives to Consider
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Supabase Pro&lt;/strong&gt; - Just pay for custom domains (~$25/mo)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom JWT Integration&lt;/strong&gt; - Complex but allows direct frontend calls&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Custom SMTP&lt;/strong&gt; - Fixes email branding (not OAuth)&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Client-side security isn't real security.&lt;/strong&gt; When you remove Supabase Auth, you must implement proper backend authorization. The extra code is worth it for the peace of mind.&lt;/p&gt;

&lt;p&gt;Key principle: &lt;strong&gt;Never trust the frontend. Always verify on the server.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Live example:&lt;/strong&gt; I've been running this architecture on &lt;a href="https://toolshubkit.com" rel="noopener noreferrer"&gt;ToolsHubKit.com&lt;/a&gt; for months without issues.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Questions?&lt;/strong&gt; Drop a comment below! Happy to review your implementation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Resources
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://next-auth.js.org/" rel="noopener noreferrer"&gt;NextAuth.js Docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://supabase.com/docs/guides/auth/security-best-practices" rel="noopener noreferrer"&gt;Supabase Security Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://owasp.org/www-project-api-security/" rel="noopener noreferrer"&gt;OWASP API Security&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;Found this helpful?&lt;/strong&gt; Give it a ❤️ and follow for more Next.js security tips!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>security</category>
      <category>web</category>
      <category>database</category>
    </item>
    <item>
      <title>🚀 Privacy-First Developer Tools: Why We Built ToolsHubKit</title>
      <dc:creator>Ali Zaib</dc:creator>
      <pubDate>Wed, 04 Feb 2026 19:05:44 +0000</pubDate>
      <link>https://dev.to/ali_zaib_randhawa/privacy-first-developer-tools-why-we-built-toolshubkit-411p</link>
      <guid>https://dev.to/ali_zaib_randhawa/privacy-first-developer-tools-why-we-built-toolshubkit-411p</guid>
      <description>&lt;p&gt;In an era where every click is tracked and every input is potentially harvested for AI training or advertising, where can developers go for simple, reliable utilities without the "privacy tax"?&lt;/p&gt;

&lt;h2&gt;
  
  
  Today, I’m excited to share &lt;strong&gt;&lt;a href="http://toolshubkit.com/" rel="noopener noreferrer"&gt;ToolsHubKit&lt;/a&gt;&lt;/strong&gt;—a comprehensive suite of developer tools built with a radical "Local-First" philosophy.
&lt;/h2&gt;

&lt;h2&gt;
  
  
  🛡️ The Privacy-First Manifest
&lt;/h2&gt;

&lt;p&gt;The core problem with many online "utility" sites is the hidden backend. When you paste an API response to format it or generate a Dockerfile, where does that data go? Often, it’s logged, stored, or analyzed.&lt;br&gt;
&lt;strong&gt;At ToolsHubKit, your data never leaves your browser.&lt;/strong&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Local Processing by Default
&lt;/h3&gt;

&lt;p&gt;Whether you are using our &lt;a href="http://toolshubkit.com/tool/dockerfile-generator" rel="noopener noreferrer"&gt;Dockerfile Generator&lt;/a&gt; or the &lt;a href="http://toolshubkit.com/tool/emoji-picker" rel="noopener noreferrer"&gt;Emoji Picker&lt;/a&gt;, the logic is executed client-side. We use modern Web APIs to ensure that your configurations, code snippets, and even sensitive strings remain strictly within your local environment.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Zero-Tracking Philosophy
&lt;/h3&gt;

&lt;p&gt;We believe tools should be transparent. No intrusive analytics, no data harvesting. Just high-fidelity tools that work instantly.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Future-Proof Consent
&lt;/h3&gt;

&lt;p&gt;As we expand ToolsHubKit to include even more powerful features (like cloud syncing or collaborative blueprints), we make a solemn promise:&lt;/p&gt;

&lt;blockquote&gt;
&lt;h2&gt;
  
  
  If a feature requires data to reach our servers, it will be used &lt;strong&gt;solely&lt;/strong&gt; for the purpose of that feature. No third-party sales, no data-mining. Full transparency and explicit user consent are our baseline, not an afterthought.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  🛠️ What’s Inside the Kit?
&lt;/h2&gt;

&lt;p&gt;We’ve curated a range of tools that solve daily developer friction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="http://toolshubkit.com/tool/dockerfile-generator" rel="noopener noreferrer"&gt;Advanced Dockerfile Generator&lt;/a&gt;&lt;/strong&gt;: Create production-ready blueprints for Node.js, Python, Rust, Java, and more with support for &lt;code&gt;ARG&lt;/code&gt;, &lt;code&gt;HEALTHCHECK&lt;/code&gt;, and &lt;code&gt;USER&lt;/code&gt; instructions.&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="http://toolshubkit.com/tool/emoji-picker" rel="noopener noreferrer"&gt;Unicode 15.0 Emoji Library&lt;/a&gt;&lt;/strong&gt;: A massive, searchable library with skin-tone support and multiple copy formats (Hex, HTML, CSS).&lt;/li&gt;
&lt;li&gt;  &lt;strong&gt;&lt;a href="http://toolshubkit.com/tool/invoice-generator" rel="noopener noreferrer"&gt;Invoice &amp;amp; Credit Note Tools&lt;/a&gt;&lt;/strong&gt;: Professional templates for freelancers and agencies, keeping all financial data local.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  *   &lt;strong&gt;And 40+ more utilities&lt;/strong&gt; ranging from JWT decoders to Cron builders.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  💎 Local Data, Global Impact
&lt;/h2&gt;

&lt;p&gt;By keeping tools local, we don't just protect privacy; we increase &lt;strong&gt;speed&lt;/strong&gt;. There are no round-trips to a server. Everything is instant. &lt;br&gt;
We are building ToolsHubKit to be the "Swiss Army Knife" for the modern developer. No fluff, no trackers—just the code you need to move fast.&lt;/p&gt;
&lt;h3&gt;
  
  
  Check it out and let us know what you think:
&lt;/h3&gt;

&lt;p&gt;👉 &lt;strong&gt;&lt;a href="http://toolshubkit.com/" rel="noopener noreferrer"&gt;Explore ToolsHubKit&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>webdev</category>
      <category>productivity</category>
      <category>privacy</category>
      <category>javascript</category>
    </item>
  </channel>
</rss>
