Authentication & Authorization

Troubleshooting authentication flows, user sessions, permissions, and security issues.

Authentication & Authorization

Comprehensive guide to resolving authentication and authorization issues in your application.

Authentication Issues

Login Problems

Symptoms:

  • Users cannot sign in
  • Login form not responding
  • Authentication errors

Solutions:

// Proper login error handling
export async function signIn(email: string, password: string) {
  try {
    const { data, error } = await supabase.auth.signInWithPassword({
      email,
      password,
    })

    if (error) {
      console.error('Sign in error:', error)
      
      // Handle specific error types
      switch (error.message) {
        case 'Invalid login credentials':
          throw new Error('Invalid email or password')
        case 'Email not confirmed':
          throw new Error('Please confirm your email address')
        case 'Too many requests':
          throw new Error('Too many login attempts. Please try again later.')
        default:
          throw new Error('Unable to sign in. Please try again.')
      }
    }

    return data
  } catch (error) {
    console.error('Authentication error:', error)
    throw error
  }
}

// Debug authentication state
export function useAuthDebug() {
  const [authState, setAuthState] = useState(null)

  useEffect(() => {
    // Get initial session
    supabase.auth.getSession().then(({ data: { session } }) => {
      console.log('Initial session:', session)
      setAuthState(session)
    })

    // Listen for auth changes
    const { data: { subscription } } = supabase.auth.onAuthStateChange(
      (event, session) => {
        console.log('Auth event:', event, 'Session:', session)
        setAuthState(session)
      }
    )

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

  return authState
}

Session Management Issues

Symptoms:

  • Users randomly logged out
  • Session expires too quickly
  • Authentication state inconsistent

Solutions:

// Configure session management
const supabase = createClient(supabaseUrl, supabaseKey, {
  auth: {
    autoRefreshToken: true,
    persistSession: true,
    detectSessionInUrl: true,
    flowType: 'pkce', // Use PKCE flow for better security
  },
})

// Custom session refresh logic
export function useSessionRefresh() {
  useEffect(() => {
    const refreshSession = async () => {
      const { data: { session }, error } = await supabase.auth.refreshSession()
      
      if (error) {
        console.error('Session refresh error:', error)
        // Redirect to login if refresh fails
        window.location.href = '/login'
      }
    }

    // Refresh session every 30 minutes
    const interval = setInterval(refreshSession, 30 * 60 * 1000)
    
    return () => clearInterval(interval)
  }, [])
}

// Handle session storage issues
export function clearAuthStorage() {
  // Clear Supabase auth tokens
  localStorage.removeItem('sb-' + supabaseUrl.replace('https://', '').replace('.supabase.co', '') + '-auth-token')
  
  // Clear any custom auth data
  localStorage.removeItem('user-preferences')
  sessionStorage.clear()
  
  // Force page reload
  window.location.reload()
}

Password Reset Issues

Symptoms:

  • Password reset emails not sent
  • Reset links not working
  • Email verification failures

Solutions:

// Robust password reset flow
export async function resetPassword(email: string) {
  try {
    const { error } = await supabase.auth.resetPasswordForEmail(email, {
      redirectTo: `${window.location.origin}/reset-password`,
    })

    if (error) {
      console.error('Password reset error:', error)
      
      if (error.message.includes('rate limit')) {
        throw new Error('Too many password reset requests. Please wait before trying again.')
      }
      
      throw new Error('Unable to send password reset email. Please check your email address.')
    }

    return { success: true }
  } catch (error) {
    console.error('Password reset error:', error)
    throw error
  }
}

// Handle password update
export async function updatePassword(newPassword: string) {
  try {
    const { error } = await supabase.auth.updateUser({
      password: newPassword
    })

    if (error) {
      console.error('Password update error:', error)
      throw new Error('Unable to update password. Please try again.')
    }

    return { success: true }
  } catch (error) {
    console.error('Password update error:', error)
    throw error
  }
}

// Verify reset token
export function usePasswordReset() {
  const [isValidToken, setIsValidToken] = useState(false)
  const [loading, setLoading] = useState(true)

  useEffect(() => {
    const checkResetToken = async () => {
      try {
        const { data: { session }, error } = await supabase.auth.getSession()
        
        if (error || !session) {
          setIsValidToken(false)
        } else {
          setIsValidToken(true)
        }
      } catch (error) {
        console.error('Token verification error:', error)
        setIsValidToken(false)
      } finally {
        setLoading(false)
      }
    }

    checkResetToken()
  }, [])

  return { isValidToken, loading }
}

Authorization Issues

Row Level Security (RLS) Problems

Symptoms:

  • Users see data they shouldn't
  • Database queries return no results
  • Permission denied errors

Solutions:

-- Debug RLS policies
SELECT 
  schemaname,
  tablename,
  policyname,
  permissive,
  roles,
  cmd,
  qual,
  with_check
FROM pg_policies 
WHERE tablename IN ('products', 'inventory', 'orders');

-- Create comprehensive RLS policies
-- Products table policies
CREATE POLICY "Public products are viewable by everyone" ON products
  FOR SELECT USING (is_public = true);

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

CREATE POLICY "Admins can view all products" ON products
  FOR SELECT USING (
    auth.jwt() ->> 'role' = 'admin' OR
    auth.jwt() ->> 'role' = 'manager'
  );

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

CREATE POLICY "Users can update their own products" ON products
  FOR UPDATE USING (auth.uid() = user_id)
  WITH CHECK (auth.uid() = user_id);

CREATE POLICY "Admins can delete any product" ON products
  FOR DELETE USING (
    auth.jwt() ->> 'role' = 'admin'
  );

-- Enable RLS
ALTER TABLE products ENABLE ROW LEVEL SECURITY;

-- Test RLS policies
-- This should be run as different users to test access
SELECT * FROM products; -- Test as regular user
SELECT * FROM products; -- Test as admin

Role-Based Access Control (RBAC)

Symptoms:

  • Users accessing restricted features
  • Role assignment not working
  • Permission checks failing

Solutions:

// Define user roles and permissions
export const ROLES = {
  ADMIN: 'admin',
  MANAGER: 'manager',
  USER: 'user',
  VIEWER: 'viewer',
} as const

export const PERMISSIONS = {
  PRODUCTS_CREATE: 'products:create',
  PRODUCTS_READ: 'products:read',
  PRODUCTS_UPDATE: 'products:update',
  PRODUCTS_DELETE: 'products:delete',
  INVENTORY_MANAGE: 'inventory:manage',
  ORDERS_VIEW: 'orders:view',
  ORDERS_MANAGE: 'orders:manage',
  USERS_MANAGE: 'users:manage',
} as const

// Role-permission mapping
export const ROLE_PERMISSIONS = {
  [ROLES.ADMIN]: [
    PERMISSIONS.PRODUCTS_CREATE,
    PERMISSIONS.PRODUCTS_READ,
    PERMISSIONS.PRODUCTS_UPDATE,
    PERMISSIONS.PRODUCTS_DELETE,
    PERMISSIONS.INVENTORY_MANAGE,
    PERMISSIONS.ORDERS_VIEW,
    PERMISSIONS.ORDERS_MANAGE,
    PERMISSIONS.USERS_MANAGE,
  ],
  [ROLES.MANAGER]: [
    PERMISSIONS.PRODUCTS_CREATE,
    PERMISSIONS.PRODUCTS_READ,
    PERMISSIONS.PRODUCTS_UPDATE,
    PERMISSIONS.INVENTORY_MANAGE,
    PERMISSIONS.ORDERS_VIEW,
    PERMISSIONS.ORDERS_MANAGE,
  ],
  [ROLES.USER]: [
    PERMISSIONS.PRODUCTS_READ,
    PERMISSIONS.ORDERS_VIEW,
  ],
  [ROLES.VIEWER]: [
    PERMISSIONS.PRODUCTS_READ,
  ],
}

// Permission checking hooks
export function usePermissions() {
  const { user } = useAuth()
  
  const hasPermission = useCallback((permission: string) => {
    if (!user) return false
    
    const userRole = user.app_metadata?.role || ROLES.USER
    const rolePermissions = ROLE_PERMISSIONS[userRole] || []
    
    return rolePermissions.includes(permission)
  }, [user])

  const hasAnyPermission = useCallback((permissions: string[]) => {
    return permissions.some(permission => hasPermission(permission))
  }, [hasPermission])

  const hasAllPermissions = useCallback((permissions: string[]) => {
    return permissions.every(permission => hasPermission(permission))
  }, [hasPermission])

  return {
    hasPermission,
    hasAnyPermission,
    hasAllPermissions,
  }
}

// Protected component wrapper
export function ProtectedComponent({ 
  children, 
  permission, 
  fallback = <div>Access denied</div> 
}) {
  const { hasPermission } = usePermissions()
  
  if (!hasPermission(permission)) {
    return fallback
  }
  
  return children
}

// Usage example
export function ProductManagement() {
  return (
    <ProtectedComponent permission={PERMISSIONS.PRODUCTS_CREATE}>
      <CreateProductForm />
    </ProtectedComponent>
  )
}

JWT Token Issues

Symptoms:

  • Invalid token errors
  • Token expiration problems
  • Custom claims not working

Solutions:

// JWT token debugging
export function useJWTDebug() {
  const [tokenInfo, setTokenInfo] = useState(null)

  useEffect(() => {
    const analyzeToken = async () => {
      const { data: { session } } = await supabase.auth.getSession()
      
      if (session?.access_token) {
        try {
          // Decode JWT payload (don't do this in production for sensitive data)
          const payload = JSON.parse(
            atob(session.access_token.split('.')[1])
          )
          
          setTokenInfo({
            payload,
            expiresAt: new Date(payload.exp * 1000),
            isExpired: payload.exp * 1000 < Date.now(),
            role: payload.role,
            customClaims: payload.app_metadata,
          })
        } catch (error) {
          console.error('Token decode error:', error)
        }
      }
    }

    analyzeToken()
  }, [])

  return tokenInfo
}

// Custom claims setup
export async function setUserRole(userId: string, role: string) {
  try {
    const { error } = await supabase.auth.admin.updateUserById(userId, {
      app_metadata: { role }
    })

    if (error) {
      console.error('Role update error:', error)
      throw error
    }

    return { success: true }
  } catch (error) {
    console.error('Set user role error:', error)
    throw error
  }
}

// Token refresh with retry logic
export async function refreshTokenWithRetry(maxRetries = 3) {
  for (let i = 0; i < maxRetries; i++) {
    try {
      const { data, error } = await supabase.auth.refreshSession()
      
      if (error) throw error
      
      return data
    } catch (error) {
      console.error(`Token refresh attempt ${i + 1} failed:`, error)
      
      if (i === maxRetries - 1) {
        // Final attempt failed, redirect to login
        window.location.href = '/login'
        throw error
      }
      
      // Wait before retrying
      await new Promise(resolve => setTimeout(resolve, 1000 * Math.pow(2, i)))
    }
  }
}

Security Issues

CSRF Protection

// CSRF token handling
export function useCSRFToken() {
  const [csrfToken, setCSRFToken] = useState(null)

  useEffect(() => {
    // Get CSRF token from meta tag or API
    const token = document.querySelector('meta[name="csrf-token"]')?.getAttribute('content')
    setCSRFToken(token)
  }, [])

  return csrfToken
}

// Secure API requests
export async function secureApiCall(url: string, options: RequestInit = {}) {
  const { data: { session } } = await supabase.auth.getSession()
  
  if (!session?.access_token) {
    throw new Error('No valid session')
  }

  const headers = {
    'Authorization': `Bearer ${session.access_token}`,
    'Content-Type': 'application/json',
    ...options.headers,
  }

  return fetch(url, {
    ...options,
    headers,
  })
}

Input Validation

// Secure input validation
import { z } from 'zod'

export const loginSchema = z.object({
  email: z.string().email('Invalid email address'),
  password: z.string().min(8, 'Password must be at least 8 characters'),
})

export const productSchema = z.object({
  name: z.string().min(1, 'Product name is required').max(100),
  sku: z.string().regex(/^[A-Z0-9-]+$/, 'Invalid SKU format'),
  price: z.number().positive('Price must be positive'),
  description: z.string().max(1000, 'Description too long'),
})

// Sanitize user input
export function sanitizeInput(input: string): string {
  return input
    .replace(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, '')
    .replace(/javascript:/gi, '')
    .replace(/on\w+\s*=/gi, '')
    .trim()
}

Common Error Messages

"Invalid JWT token"

  • Cause: Expired or malformed token
  • Solution: Refresh session or redirect to login

"Row-level security policy violated"

  • Cause: RLS policy blocking data access
  • Solution: Check and update RLS policies

"Authentication required"

  • Cause: User not logged in or session expired
  • Solution: Redirect to login page

"Insufficient permissions"

  • Cause: User lacks required role/permissions
  • Solution: Check user roles and permissions

"Email not confirmed"

  • Cause: User hasn't verified email address
  • Solution: Resend confirmation email

For advanced authentication and security configurations, refer to the Supabase Auth Documentation.