Skip to main content

Overview

The authentication system handles user sessions, role-based access control, and guest sessions across both mobile and web platforms.

Mobile Authentication Flow

The mobile app implements a role-based authentication system with support for admin, employee (empleado), and guest users.

Session Check

On app launch, the system checks for existing sessions and routes users accordingly.
mobile/app/index.tsx
const bootstrap = async () => {
  const { data } = await supabase.auth.getSession();

  if (data.session) {
    const { data: profile } = await supabase
      .from("profiles")
      .select("role")
      .eq("id", data.session.user.id)
      .single();

    if (profile?.role === "admin") {
      router.replace("/(admin)");
      return;
    }

    if (profile?.role === "empleado") {
      router.replace("/(empleado)");
      return;
    }

    await supabase.auth.signOut();
    router.replace("/(auth)/login");
    return;
  }

  const guestSession = await SecureStore.getItemAsync("guest_session");

  if (guestSession) {
    router.replace("/(guest)/home");
    return;
  }

  router.replace("/(auth)/login");
};

Authentication Methods

Get Current Session

const { data, error } = await supabase.auth.getSession();

if (data.session) {
  console.log('User ID:', data.session.user.id);
  console.log('Email:', data.session.user.email);
}
data
object
session
Session | null
user
User
id
string
User unique identifier
email
string
User email address
role
string
Authentication role
access_token
string
JWT access token
refresh_token
string
Refresh token for session renewal

Sign In

const { data, error } = await supabase.auth.signInWithPassword({
  email: '[email protected]',
  password: 'password123'
});
email
string
required
User email address
password
string
required
User password

Sign Out

await supabase.auth.signOut();
router.replace("/(auth)/login");

Role-Based Access Control

The app supports three user roles:
admin
string
Administrator role with full system access. Routes to /(admin) layout.
empleado
string
Employee role with limited access. Routes to /(empleado) layout.
guest
string
Guest role for unauthenticated browsing. Uses SecureStore for session management instead of Supabase auth.

Guest Sessions

Guest sessions are managed using Expo SecureStore:
// Check for guest session
const guestSession = await SecureStore.getItemAsync("guest_session");

if (guestSession) {
  router.replace("/(guest)/home");
}

// Create guest session
await SecureStore.setItemAsync("guest_session", "true");

// Clear guest session
await SecureStore.deleteItemAsync("guest_session");

Web Authentication Flow

The web application uses Next.js middleware for lightweight authentication checks.

Middleware Authentication

The middleware checks for Supabase authentication cookies before allowing access to protected routes.
web/middleware.ts
export function middleware(request: NextRequest) {
    const { pathname } = request.nextUrl;

    if (pathname.startsWith("/dashboard")) {
        // Supabase persists the session in a cookie named "sb-<project>-auth-token"
        const hasSession = Array.from(request.cookies.getAll()).some(
            (c) => c.name.startsWith("sb-") && c.name.endsWith("-auth-token")
        );

        if (!hasSession) {
            return NextResponse.redirect(new URL("/", request.url));
        }
    }

    return NextResponse.next();
}

export const config = {
    matcher: ["/dashboard/:path*"],
};
matcher
string[]
Array of route patterns to apply middleware to. Protects all /dashboard routes and sub-routes.
Supabase authentication tokens are stored in cookies with the naming pattern:
sb-<project-ref>-auth-token
name
string
Cookie name following pattern sb-<project>-auth-token
value
string
Encrypted authentication token
httpOnly
boolean
default:true
Cookie is not accessible via JavaScript
secure
boolean
default:true
Cookie only sent over HTTPS

Server-Side Authentication

Full role validation happens server-side in protected pages:
import { createClient } from '@/lib/supabase-server'

export default async function DashboardPage() {
  const supabase = await createClient();
  const { data: { user } } = await supabase.auth.getUser();
  
  if (!user) {
    redirect('/');
  }
  
  const { data: profile } = await supabase
    .from('profiles')
    .select('role')
    .eq('id', user.id)
    .single();
  
  // Validate role and render accordingly
}

Profile Schema

User profiles are stored in the profiles table:
interface Profile {
  id: string;           // Matches auth.users.id
  role: 'admin' | 'empleado';  // User role
  created_at: string;   // ISO timestamp
  updated_at: string;   // ISO timestamp
}

Best Practices

Mobile

  • Always check for valid sessions on app launch
  • Validate user roles before routing
  • Sign out users with invalid or missing roles
  • Use SecureStore for guest session management
  • Clear guest sessions when users sign in

Web

  • Use middleware for initial authentication checks
  • Perform full role validation server-side
  • Read user data from cookies in server components
  • Redirect unauthenticated users to login page
  • Never trust client-side authentication alone

Error Handling

// Mobile authentication with error handling
try {
  const { data, error } = await supabase.auth.getSession();
  
  if (error) {
    console.error('Session error:', error);
    router.replace('/(auth)/login');
    return;
  }
  
  if (!data.session) {
    router.replace('/(auth)/login');
    return;
  }
  
  // Proceed with authenticated flow
} catch (err) {
  console.error('Authentication failed:', err);
  router.replace('/(auth)/login');
}

Build docs developers (and LLMs) love