Overview
Portal Ciudadano Manta uses Supabase Auth for secure authentication with email/password, session management, and role-based access control.
Setup Configure Supabase client and environment variables
User Flow Registration, login, and session management
Security PKCE flow, session persistence, and best practices
Role-Based Access Citizen and administrator roles
Environment Variables
Configure Supabase credentials in your .env file:
VITE_SUPABASE_URL = https://your-project.supabase.co
VITE_SUPABASE_ANON_KEY = your-anon-key
Never commit your .env file to version control. The anon key is safe for client-side use but should still be kept in environment variables.
Client Configuration
The Supabase client is pre-configured in src/lib/supabase.ts:
import { createClient } from "@supabase/supabase-js"
import type { Database } from "../types/database.types"
import { conditionalStorage } from "./storage"
const supabaseUrl = import . meta . env . VITE_SUPABASE_URL
const supabaseAnonKey = import . meta . env . VITE_SUPABASE_ANON_KEY
export const supabase = createClient < Database >(
supabaseUrl || "https://placeholder.supabase.co" ,
supabaseAnonKey || "placeholder-key" ,
{
auth: {
persistSession: true ,
autoRefreshToken: true ,
detectSessionInUrl: true ,
storage: conditionalStorage , // Falls back to memory if localStorage unavailable
flowType: "pkce" , // PKCE flow for enhanced security
},
db: {
schema: "public" ,
},
global: {
headers: {
"X-Client-Info" : "[email protected] " ,
},
},
realtime: {
timeout: 10000 ,
},
}
)
Key Configuration Options:
persistSession : true - Sessions persist across page refreshes
autoRefreshToken : true - Automatically refreshes expired tokens
flowType : "pkce" - Uses PKCE flow for enhanced security in SPAs
storage : Custom conditional storage that falls back to memory
Authentication Flow
Initialize Authentication
Call this on app startup (typically in main.ts or root component):
import { useAuthStore } from '@/stores/auth.store'
const authStore = useAuthStore ()
// Initialize and setup auth state listeners
await authStore . initAuth ()
This will:
Check for existing session in storage
Load user profile if authenticated
Setup auth state change listeners
Handle token refresh automatically
User Registration
Citizen Registration
const authStore = useAuthStore ()
const result = await authStore . register (
'[email protected] ' ,
'SecurePassword123!' ,
{
nombres: 'Juan' ,
apellidos: 'Pérez' ,
cedula: '1234567890' ,
parroquia: 'Manta' ,
barrio: 'Centro' ,
tipo: 'ciudadano'
}
)
if ( result . success ) {
console . log ( 'Registration successful!' )
// User will receive verification email
} else {
console . error ( 'Registration failed:' , result . error )
}
After registration, users receive a verification email. The email contains a link that redirects to /login?verified=true.
Administrator Registration
const result = await authStore . register (
'[email protected] ' ,
'AdminPassword123!' ,
{
nombres: 'MarÃa' ,
apellidos: 'González' ,
cedula: '0987654321' ,
tipo: 'administrador'
}
)
Registration Flow:
Create account in Supabase Auth
Store user metadata (nombres, apellidos, etc.)
Insert record in usuarios table (for ciudadanos)
Insert record in administradores table (for administrators)
Send verification email
User Login
const authStore = useAuthStore ()
const result = await authStore . login (
'[email protected] ' ,
'password123'
)
if ( result . success ) {
console . log ( 'Login successful!' )
console . log ( 'User type:' , authStore . usuario ?. tipo )
// Navigate based on user type
if ( authStore . isAdministrador ()) {
router . push ( '/admin' )
} else {
router . push ( '/dashboard' )
}
} else {
console . error ( 'Login failed:' , result . error )
}
Login Process:
Authenticate with Supabase Auth
Retrieve session and user object
Fetch extended profile from usuarios or administradores table
Store session in localStorage (or memory fallback)
Setup realtime auth state listeners
Password Reset
const authStore = useAuthStore ()
const result = await authStore . resetPassword ( '[email protected] ' )
if ( result . success ) {
console . log ( 'Reset email sent!' )
} else {
console . error ( 'Reset failed:' , result . error )
}
Users receive an email with a reset link that redirects to /reset-password with a token.
const authStore = useAuthStore ()
const result = await authStore . logout ()
if ( result . success ) {
console . log ( 'Logged out successfully' )
router . push ( '/login' )
}
Logout Process:
Clear local state immediately
Call Supabase signOut() with 5-second timeout
Clear session from storage
Navigate to login page
Logout has a 5-second timeout to prevent hanging. Local state is cleared immediately regardless of timeout.
Security
PKCE Flow
The client uses PKCE (Proof Key for Code Exchange) flow for enhanced security:
{
auth : {
flowType : "pkce"
}
}
PKCE protects against authorization code interception attacks and is recommended for SPAs.
Session Management
Sessions are automatically managed:
Persistence : Stored in localStorage (or memory fallback)
Auto-refresh : Tokens refresh automatically before expiration
Timeout : Realtime operations have 10-second timeout
Validation : Session checked on app init and route changes
// Check if user is authenticated
const isAuthed = await isAuthenticated ()
// Get current user
const user = await getCurrentUser ()
Auth State Listeners
The auth store automatically listens for auth state changes:
supabase . auth . onAuthStateChange ( async ( event , session ) => {
switch ( event ) {
case 'SIGNED_IN' :
// User signed in
user . value = session ?. user ?? null
await fetchUsuario ( session . user . id )
break
case 'SIGNED_OUT' :
// User signed out
user . value = null
usuario . value = null
break
case 'TOKEN_REFRESHED' :
// Token refreshed automatically
user . value = session ?. user ?? null
break
case 'USER_UPDATED' :
// User data updated
user . value = session ?. user ?? null
await fetchUsuario ( session . user . id )
break
}
})
Error Handling
Use the error handler helper:
import { handleSupabaseError } from '@/lib/supabase'
const { data , error } = await supabase
. from ( 'reportes' )
. select ( '*' )
if ( error ) {
const userMessage = handleSupabaseError ( error )
// Display userMessage to user
}
Role-Based Access
User Types
The platform supports two user types:
Ciudadano (Citizen)
Submit reports
View news and surveys
Update profile
Stored in usuarios table
Administrador (Administrator)
All citizen permissions
Manage reports (review, update status)
Create/edit/delete news
Create/edit/delete surveys
View statistics and analytics
Stored in administradores table
Checking User Role
const authStore = useAuthStore ()
if ( authStore . isAuthenticated ()) {
if ( authStore . isCiudadano ()) {
console . log ( 'User is a citizen' )
}
if ( authStore . isAdministrador ()) {
console . log ( 'User is an administrator' )
}
// Access user data
console . log ( authStore . usuario . nombres )
console . log ( authStore . usuario . tipo )
}
Route Protection
Protect routes in your router:
import { useAuthStore } from '@/stores/auth.store'
router . beforeEach ( async ( to , from , next ) => {
const authStore = useAuthStore ()
// Protected routes
if ( to . meta . requiresAuth && ! authStore . isAuthenticated ()) {
return next ( '/login' )
}
// Admin-only routes
if ( to . meta . requiresAdmin && ! authStore . isAdministrador ()) {
return next ( '/dashboard' )
}
next ()
})
Database Schema
usuarios Table
CREATE TABLE usuarios (
id UUID PRIMARY KEY REFERENCES auth . users (id),
email TEXT NOT NULL ,
nombres TEXT NOT NULL ,
apellidos TEXT NOT NULL ,
cedula TEXT NOT NULL UNIQUE ,
parroquia TEXT ,
barrio TEXT ,
tipo TEXT NOT NULL CHECK (tipo IN ( 'ciudadano' , 'administrador' )),
activo BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW (),
updated_at TIMESTAMPTZ DEFAULT NOW ()
);
administradores Table
CREATE TABLE administradores (
id UUID PRIMARY KEY REFERENCES auth . users (id),
email TEXT NOT NULL ,
nombres TEXT NOT NULL ,
apellidos TEXT NOT NULL ,
cedula TEXT NOT NULL UNIQUE ,
activo BOOLEAN DEFAULT true,
created_at TIMESTAMPTZ DEFAULT NOW (),
updated_at TIMESTAMPTZ DEFAULT NOW ()
);
Advanced Usage
Profile Updates
const authStore = useAuthStore ()
const result = await authStore . updateProfile ({
nombres: 'Juan Carlos' ,
parroquia: 'Nueva Parroquia' ,
barrio: 'Nuevo Barrio'
})
if ( result . success ) {
console . log ( 'Profile updated!' )
console . log ( authStore . usuario ) // Updated data
}
Fetch User Profile
Manually fetch user profile (usually done automatically):
const authStore = useAuthStore ()
if ( authStore . user ) {
const profile = await authStore . fetchUsuario ( authStore . user . id )
console . log ( profile )
}
The function automatically:
Checks usuarios table first
Falls back to administradores table if not found
Handles concurrent fetch requests with locking
Has 10-second timeout for reliability
Session Validation
import { supabase } from '@/lib/supabase'
// Get current session
const { data : { session }, error } = await supabase . auth . getSession ()
if ( session ) {
console . log ( 'Session valid until:' , session . expires_at )
console . log ( 'User:' , session . user )
} else {
console . log ( 'No active session' )
}
// Get user (validates token)
const { data : { user }, error : userError } = await supabase . auth . getUser ()
Best Practices
Always initialize auth on app startup
// In main.ts or App.vue
const authStore = useAuthStore ()
await authStore . initAuth ()
This ensures session restoration and proper auth state listeners.
Use computed properties for reactive auth checks
import { computed } from 'vue'
import { useAuthStore } from '@/stores/auth.store'
const authStore = useAuthStore ()
const isAdmin = computed (() => authStore . isAdministrador ())
// In template
< div v - if = "isAdmin" > Admin only content </ div >
const authStore = useAuthStore ()
const handleLogin = async () => {
const result = await authStore . login ( email , password )
if ( authStore . loading ) {
// Show spinner
}
if ( authStore . error ) {
// Display error
console . error ( authStore . error )
}
}
Always protect authenticated and admin routes in your router configuration.
Clear sensitive data on logout
The logout function automatically clears all auth state. Ensure you also clear any cached user data in other stores.
Validate sessions periodically
For sensitive operations, validate the session is still valid: const { data : { session } } = await supabase . auth . getSession ()
if ( ! session ) {
// Redirect to login
await authStore . logout ()
router . push ( '/login' )
}
Troubleshooting
Common Issues
Session not persisting:
Verify localStorage is available
Check browser privacy settings
Storage falls back to memory if localStorage is blocked
Token refresh failing:
Check Supabase project settings
Verify autoRefreshToken: true in config
Check network connectivity
User profile not loading:
Verify user exists in usuarios or administradores table
Check database permissions
Review console logs for error details
Login timeout:
Check Supabase service status
Verify network connection
Review timeout settings in config
API Reference View complete API documentation for all composables and stores