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.