Vue + Supabase Security Blueprint

Share

To secure a Vue + Supabase SPA, you need to: (1) enable RLS on all tables since Vue SPAs have no server layer, (2) use Pinia for reactive auth state management, (3) implement vue-router navigation guards for UX (not security), (4) use only the anon key in client code, and (5) test RLS policies thoroughly before launch. This blueprint covers Vue 3 Composition API patterns with Supabase.

TL;DR

Vue SPAs with Supabase rely entirely on RLS for data security. Key tasks: enable RLS on all tables, use Pinia for reactive auth state, add vue-router navigation guards for UX (not security), and remember that route guards are client-side only-RLS is your actual security layer.

RLS Configuration Supabase

RLS policies for Vue SPA
-- Users can only access their own data
CREATE POLICY "User data isolation"
  ON user_data FOR ALL
  USING (auth.uid() = user_id);

-- Public read, authenticated write
CREATE POLICY "Public posts"
  ON posts FOR SELECT
  USING (true);

CREATE POLICY "Authenticated create"
  ON posts FOR INSERT
  WITH CHECK (auth.uid() = author_id);

Auth Store with Pinia Vue

stores/auth.ts
import { defineStore } from 'pinia'
import { supabase } from '@/lib/supabase'

export const useAuthStore = defineStore('auth', {
  state: () => ({
    user: null as User | null,
    loading: true,
  }),

  actions: {
    async init() {
      const { data } = await supabase.auth.getSession()
      this.user = data.session?.user ?? null
      this.loading = false

      supabase.auth.onAuthStateChange((_event, session) => {
        this.user = session?.user ?? null
      })
    },
  },
})

Router Guards Vue

router/index.ts - UX only, not security
router.beforeEach(async (to, from, next) => {
  const authStore = useAuthStore()

  // Wait for auth to initialize
  if (authStore.loading) {
    await new Promise(resolve => {
      const unwatch = watch(() => authStore.loading, (loading) => {
        if (!loading) { unwatch(); resolve(true) }
      })
    })
  }

  if (to.meta.requiresAuth && !authStore.user) {
    next('/login')
  } else {
    next()
  }
})

Router guards are UX only. Any JavaScript can be disabled or bypassed. Your Supabase RLS policies are the only real security layer.

Security Checklist

Pre-Launch Checklist

RLS enabled on all tables

RLS policies tested thoroughly

Auth state managed with Pinia

Only anon key in client code

Environment variables configured

Alternative Stacks

Consider these related blueprints:

Security Blueprints

Vue + Supabase Security Blueprint