Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Jesus-Puertos/h-ayuntamiento/llms.txt

Use this file to discover all available pages before exploring further.

Overview

The Ayuntamiento de Zongolica platform uses Supabase Auth for secure user authentication and session management. The system supports multiple authentication methods to accommodate different user preferences.

OAuth Providers

Google and Facebook social login for quick access

Email/Password

Traditional email and password authentication

Session Management

Secure JWT-based sessions with automatic refresh

Row Level Security

Database-level security for user data isolation

Supabase Auth Integration

The authentication system is built on top of Supabase Auth, providing enterprise-grade security out of the box.

Client Configuration

Location: src/lib/supabase.ts
src/lib/supabase.ts
import { createClient } from '@supabase/supabase-js';

// Environment variables
const supabaseUrl = import.meta.env.PUBLIC_SUPABASE_URL || '';
const supabaseAnonKey = import.meta.env.PUBLIC_SUPABASE_ANON_KEY || '';

export const supabase = createClient(supabaseUrl, supabaseAnonKey);
Never expose your Supabase service role key in client-side code. Always use the anon/public key.

Environment Variables

Configure in .env:
.env
PUBLIC_SUPABASE_URL=https://xxxxxxxxxxxxx.supabase.co
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
These are prefixed with PUBLIC_ to make them available in the browser.

OAuth Providers

Google OAuth

The platform supports “Sign in with Google” for quick authentication.

Setup Process

1

Create Google Cloud Project

  1. Go to console.cloud.google.com
  2. Create a new project or select an existing one
  3. Enable the Google+ API
2

Create OAuth Credentials

  1. Navigate to Credentials in the left sidebar
  2. Click Create Credentials > OAuth 2.0 Client ID
  3. Choose Web application
  4. Add authorized redirect URI:
    https://your-project.supabase.co/auth/v1/callback
    
  5. Copy the Client ID and Client Secret
3

Configure in Supabase

  1. Go to your Supabase project dashboard
  2. Navigate to Authentication > Providers
  3. Find Google and toggle it on
  4. Paste your Client ID and Client Secret
  5. Click Save

Implementation

src/lib/supabase.ts
export async function signInWithGoogle(redirectPath = '/turismo?onboarding=1') {
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
      redirectTo: `${window.location.origin}/auth/callback?return_url=${encodeURIComponent(redirectPath)}`
    }
  });
  return { data, error };
}
Usage in components:
import { signInWithGoogle } from '@/lib/supabase';

async function handleGoogleLogin() {
  const { error } = await signInWithGoogle('/turismo');
  if (error) {
    console.error('Login failed:', error.message);
  }
}

Facebook OAuth

Similar setup process for Facebook authentication.

Setup Process

1

Create Facebook App

  1. Go to developers.facebook.com
  2. Click My Apps > Create App
  3. Choose Consumer as app type
  4. Fill in app details
2

Add Facebook Login Product

  1. In your app dashboard, click Add Product
  2. Find Facebook Login and click Set Up
  3. Choose Web platform
  4. Add valid OAuth redirect URI:
    https://your-project.supabase.co/auth/v1/callback
    
3

Configure in Supabase

  1. Go to Settings > Basic in Facebook app
  2. Copy App ID and App Secret
  3. In Supabase: Authentication > Providers > Facebook
  4. Enable Facebook and paste credentials
  5. Click Save

Email/Password Authentication

Sign Up

src/lib/supabase.ts
export async function signUpWithEmail(
  email: string, 
  password: string, 
  name?: string
) {
  const { data, error } = await supabase.auth.signUp({
    email,
    password,
    options: {
      data: {
        name: name || email.split('@')[0]
      }
    }
  });
  return { data, error };
}

Sign In

src/lib/supabase.ts
export async function signInWithEmail(email: string, password: string) {
  const { data, error } = await supabase.auth.signInWithPassword({
    email,
    password
  });
  return { data, error };
}

Usage Example

React Component
import { signInWithEmail, signUpWithEmail } from '@/lib/supabase';
import { useState } from 'react';

function LoginForm() {
  const [email, setEmail] = useState('');
  const [password, setPassword] = useState('');
  const [isSignUp, setIsSignUp] = useState(false);

  async function handleSubmit(e: React.FormEvent) {
    e.preventDefault();
    
    const authFn = isSignUp ? signUpWithEmail : signInWithEmail;
    const { error } = await authFn(email, password);
    
    if (error) {
      alert(error.message);
    } else {
      window.location.href = '/turismo';
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <input 
        type="email" 
        value={email} 
        onChange={e => setEmail(e.target.value)}
        placeholder="Email"
        required
      />
      <input 
        type="password" 
        value={password} 
        onChange={e => setPassword(e.target.value)}
        placeholder="Password"
        required
      />
      <button type="submit">
        {isSignUp ? 'Sign Up' : 'Sign In'}
      </button>
      <button type="button" onClick={() => setIsSignUp(!isSignUp)}>
        {isSignUp ? 'Already have an account?' : 'Need an account?'}
      </button>
    </form>
  );
}

OAuth Callback Handler

Location: src/pages/auth/callback.astro Handles the OAuth redirect after successful authentication:
src/pages/auth/callback.astro
---
export const prerender = false;

const { searchParams } = Astro.url;
const returnUrl = searchParams.get('return_url') || '/turismo';

// Supabase automatically handles the token exchange
// Just redirect to the intended destination
---

<script define:vars={{ returnUrl }}>
  // Redirect after a brief moment to ensure session is set
  setTimeout(() => {
    window.location.href = returnUrl;
  }, 100);
</script>

<div class="flex items-center justify-center min-h-screen">
  <div class="text-center">
    <div class="animate-spin rounded-full h-12 w-12 border-b-2 border-[#ff8200] mx-auto"></div>
    <p class="mt-4 text-gray-600">Signing you in...</p>
  </div>
</div>

Session Management

Getting Current User

src/lib/supabase.ts
export async function getCurrentUser() {
  const { data: { user } } = await supabase.auth.getUser();
  return user;
}

Checking Authentication Status

In Astro pages:
---
import { supabase } from '@/lib/supabase';

const { data: { session } } = await supabase.auth.getSession();
const user = session?.user;

if (!user) {
  return Astro.redirect('/auth/login');
}
---
In React components:
import { useEffect, useState } from 'react';
import { supabase } from '@/lib/supabase';

function useAuth() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      setUser(session?.user ?? null);
      setLoading(false);
    });

    // Listen for auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (_event, session) => {
        setUser(session?.user ?? null);
      }
    );

    return () => subscription.unsubscribe();
  }, []);

  return { user, loading };
}

Sign Out

src/lib/supabase.ts
export async function signOut() {
  const { error } = await supabase.auth.signOut();
  return { error };
}
Usage:
import { signOut } from '@/lib/supabase';

async function handleSignOut() {
  await signOut();
  window.location.href = '/';
}

User Profiles

Location: src/lib/supabase.ts

Profile Schema

export interface UserProfile {
  id: string;                    // Matches auth.users.id
  email: string;
  full_name: string;
  avatar_url: string;
  provider: string;              // 'google', 'facebook', 'email'
  onboarding_completed: boolean;
  created_at: string;
  updated_at: string;
}

Database Table

CREATE TABLE user_profiles (
  id UUID REFERENCES auth.users(id) PRIMARY KEY,
  email TEXT,
  full_name TEXT,
  avatar_url TEXT,
  provider TEXT,
  onboarding_completed BOOLEAN DEFAULT false,
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);

Profile Management Functions

src/lib/supabase.ts
export async function getUserProfile(userId: string) {
  const { data, error } = await supabase
    .from('user_profiles')
    .select('*')
    .eq('id', userId)
    .single();
  return { data: data as UserProfile | null, error };
}

export async function upsertUserProfile(
  profile: Partial<UserProfile> & { id: string }
) {
  const { data, error } = await supabase
    .from('user_profiles')
    .upsert([{ ...profile, updated_at: new Date().toISOString() }])
    .select()
    .single();
  return { data, error };
}

export async function markOnboardingCompleted(userId: string) {
  const { data, error } = await supabase
    .from('user_profiles')
    .update({ 
      onboarding_completed: true, 
      updated_at: new Date().toISOString() 
    })
    .eq('id', userId)
    .select()
    .single();
  return { data, error };
}

Protected Routes

Server-Side Protection

Protect pages at the server level:
src/pages/turismo/perfil.astro
---
import { supabase } from '@/lib/supabase';
import Layout from '@/layouts/Layout.astro';

export const prerender = false;

// Check authentication
const { data: { session } } = await supabase.auth.getSession();

if (!session) {
  return Astro.redirect('/auth/login?redirect=/turismo/perfil');
}

const user = session.user;
---

<Layout title="Mi Perfil">
  <h1>Welcome, {user.email}</h1>
  <!-- Profile content -->
</Layout>

Client-Side Protection

For React components:
import { useEffect } from 'react';
import { supabase } from '@/lib/supabase';

function ProtectedComponent() {
  useEffect(() => {
    supabase.auth.getSession().then(({ data: { session } }) => {
      if (!session) {
        window.location.href = '/auth/login';
      }
    });
  }, []);

  return <div>Protected content</div>;
}

Row Level Security (RLS)

Supabase RLS ensures users can only access their own data.

User Profiles Policies

-- Users can view their own profile
CREATE POLICY "Users can view own profile" ON user_profiles
  FOR SELECT USING (auth.uid() = id);

-- Users can update their own profile
CREATE POLICY "Users can update own profile" ON user_profiles
  FOR UPDATE USING (auth.uid() = id);

-- Users can insert their own profile
CREATE POLICY "Users can insert own profile" ON user_profiles
  FOR INSERT WITH CHECK (auth.uid() = id);

User Preferences Policies

-- Users can view their own preferences
CREATE POLICY "Users can view own preferences" ON user_preferences
  FOR SELECT USING (auth.uid() = user_id);

-- Users can insert their own preferences
CREATE POLICY "Users can insert own preferences" ON user_preferences
  FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Users can update their own preferences
CREATE POLICY "Users can update own preferences" ON user_preferences
  FOR UPDATE USING (auth.uid() = user_id);

User Routes Policies

-- Anyone can view routes (for sharing)
CREATE POLICY "Anyone can view routes" ON user_routes
  FOR SELECT USING (true);

-- Users can insert their own routes
CREATE POLICY "Users can insert own routes" ON user_routes
  FOR INSERT WITH CHECK (auth.uid() = user_id);

-- Users can update their own routes
CREATE POLICY "Users can update own routes" ON user_routes
  FOR UPDATE USING (auth.uid() = user_id);

Email Configuration

Enable Email Provider

1

Enable in Supabase

  1. Go to Authentication > Providers
  2. Find Email and toggle it on
  3. Configure settings:
    • Confirm email: Enable for production, disable for development
    • Secure email change: Recommended
    • Double confirm email: Optional
2

Customize Email Templates (Optional)

  1. Go to Authentication > Email Templates
  2. Customize:
    • Confirmation email
    • Magic link email
    • Password reset email
  3. Add your branding and custom copy

Email Verification Flow

When email confirmation is enabled:
  1. User signs up
  2. Supabase sends confirmation email
  3. User clicks link in email
  4. User is redirected to site with confirmed account
  5. Profile is created and onboarding begins

Security Best Practices

Use HTTPS Only

Always enforce SSL/TLS encryption in production

Validate Redirects

Sanitize redirect URLs to prevent open redirect vulnerabilities

Rate Limiting

Enable Supabase rate limiting to prevent abuse

Strong Passwords

Enforce minimum password requirements

Redirect Validation

function sanitizeRedirectUrl(url: string): string {
  const allowedPaths = ['/turismo', '/perfil', '/mi-ruta'];
  const parsed = new URL(url, window.location.origin);
  
  // Only allow same-origin redirects
  if (parsed.origin !== window.location.origin) {
    return '/turismo';
  }
  
  // Optionally restrict to specific paths
  const path = parsed.pathname;
  if (!allowedPaths.some(p => path.startsWith(p))) {
    return '/turismo';
  }
  
  return url;
}

Troubleshooting

Symptoms: User is not redirected after OAuth loginSolutions:
  1. Verify redirect URI matches exactly in Google/Facebook console
  2. Check that callback page exists at /auth/callback
  3. Ensure return_url parameter is properly encoded
  4. Check browser console for errors
Symptoms: User is logged out on page refreshSolutions:
  1. Check that Supabase client is properly initialized
  2. Verify cookies are enabled in browser
  3. Ensure localStorage is available
  4. Check for CORS issues in browser console
Symptoms: Queries return empty results or permission errorsSolutions:
  1. Verify RLS policies are correctly configured
  2. Check that auth.uid() is available in policies
  3. Test policies in Supabase SQL editor
  4. Ensure user is properly authenticated before queries
Symptoms: Users don’t receive confirmation emailsSolutions:
  1. Check spam/junk folder
  2. Verify email provider in Supabase (default is limited)
  3. Configure custom SMTP in Supabase settings
  4. Disable email confirmation for development

Next Steps

Tourism System

Learn about the onboarding flow and user features

Government Portal

Explore public-facing government pages

Build docs developers (and LLMs) love