I’m running into an issue where posting to my Supabase projects table works fine locally but fails in production on Vercel with an “Authentication required” error. From what I can tell, the API route isn’t reading the Supabase session cookies after deploy, even though they’re set correctly when I’m logged in. How do I properly configure my Next.js API routes on Vercel so Supabase Auth cookies are passed through and the user session is available? I have litterally worked on fixing this by troubleshooting through vercel, chatgpt, claude and absolutely nothing works. Basically I am able to post project on my platform which is a project posting platform before i deploy but the second i deploy it, it say Authentication error. Is anyone else running into this issue if so I would love some help thanks!
Below is my supabase and middlewear code if you could help that would be great:
supabase.ts
import { createClient } from “@supabase/supabase-js”
// Check if environment variables are available
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
if (!supabaseUrl || !supabaseAnonKey) {
throw new Error(“Missing Supabase environment variables”)
}
export const supabase = createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: true,
persistSession: true,
detectSessionInUrl: true,
},
})
// Database types
export interface User {
id: string
email: string
role: “flipper” | “contractor”
created_at: string
updated_at: string
}
export interface Profile {
id: string
user_id: string
first_name: string
last_name: string
phone?: string
company_name?: string
business_type?: string
years_experience?: number
license_number?: string
insurance_provider?: string
service_radius?: number
team_size?: string
hourly_rate_min?: number
hourly_rate_max?: number
average_budget_min?: number
average_budget_max?: number
projects_per_year?: string
working_hours?: string
communication_preference?: string
project_notifications: boolean
marketing_emails: boolean
profile_photo_url?: string
business_license_url?: string
insurance_url?: string
bio?: string
verified: boolean
rating: number
reviews_count: number
completed_jobs: number
response_time_hours: number
availability: string
created_at: string
updated_at: string
}
export interface Project {
id: string
user_id: string
title: string
description: string
detailed_description?: string
location: string
budget_min?: number
budget_max?: number
timeline_weeks?: number
urgency: “low” | “medium” | “high”
status: “open” | “in_progress” | “completed” | “cancelled”
featured: boolean
bids_count: number
created_at: string
updated_at: string
}
export interface Trade {
id: string
name: string
created_at: string
}
export interface Language {
id: string
name: string
created_at: string
}
export interface Bid {
id: string
project_id: string
contractor_id: string
amount: number
message?: string
status: “pending” | “accepted” | “rejected”
created_at: string
updated_at: string
}
// Client-side Supabase instance getter (singleton pattern)
export const getClientSupabase = () => {
return supabase
}
// Re-export createClient for convenience
export { createClient } from “@supabase/supabase-js”
supabase server.ts:
import { createClient } from “@supabase/supabase-js”
import { cookies } from “next/headers”
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseServiceKey = process.env.SUPABASE_SERVICE_ROLE_KEY!
if (!supabaseUrl || !supabaseServiceKey) {
throw new Error(“Missing Supabase server environment variables”)
}
// Server-side Supabase client with service role key
export async function createServerSupabaseClient() {
const cookieStore = await cookies()
return createClient(supabaseUrl, supabaseServiceKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
global: {
headers: {
Authorization: `Bearer ${supabaseServiceKey}`,
},
},
})
}
// Alternative client for user authentication
export async function createServerSupabaseClientForAuth() {
const cookieStore = await cookies()
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
return createClient(supabaseUrl, supabaseAnonKey, {
auth: {
autoRefreshToken: false,
persistSession: false,
},
global: {
headers: {
Cookie: cookieStore.toString(),
},
},
})
}
supabase auth.ts:
import { supabase } from “./supabase”
export interface AuthUser {
id: string
email: string
role: “flipper” | “contractor”
profile: {
firstName: string
lastName: string
companyName?: string
phone?: string
}
}
export async function signUpWithEmail(userData: {
email: string
password: string
role: “flipper” | “contractor”
firstName: string
lastName: string
companyName?: string
phone?: string
}) {
try {
console.log(“Creating new user account:”, userData.email, “as”, userData.role)
// Validate input data
if (!userData.email || !userData.password || !userData.role || !userData.firstName) {
return { success: false, error: "Please fill in all required fields." }
}
if (userData.password.length < 8) {
return { success: false, error: "Password must be at least 8 characters long." }
}
if (!userData.email.includes("@")) {
return { success: false, error: "Please enter a valid email address." }
}
// First, sign up the user
const { data, error } = await supabase.auth.signUp({
email: userData.email.trim().toLowerCase(),
password: userData.password,
options: {
data: {
role: userData.role,
first_name: userData.firstName.trim(),
last_name: userData.lastName.trim(),
company_name: userData.companyName?.trim() || "",
phone: userData.phone?.trim() || "",
},
},
})
if (error) {
console.error("Supabase signup error:", error)
// Provide user-friendly error messages
if (error.message.includes("already registered") || error.message.includes("already been registered")) {
return { success: false, error: "An account with this email already exists. Please sign in instead." }
}
if (error.message.includes("Password should be")) {
return { success: false, error: "Password must be at least 8 characters long." }
}
if (error.message.includes("Invalid email")) {
return { success: false, error: "Please enter a valid email address." }
}
if (error.message.includes("signup is disabled")) {
return { success: false, error: "Account creation is currently disabled. Please contact support." }
}
return { success: false, error: `Account creation failed: ${error.message}` }
}
if (data.user) {
console.log("User account created successfully:", data.user.id)
// Check if we have a session (user is automatically signed in)
if (data.session) {
console.log("User automatically signed in after signup")
const authUser: AuthUser = {
id: data.user.id,
email: data.user.email!,
role: userData.role,
profile: {
firstName: userData.firstName,
lastName: userData.lastName,
companyName: userData.companyName,
phone: userData.phone,
},
}
return { success: true, user: authUser }
}
// If no session but user exists, try to sign them in immediately
if (!data.session && data.user.email_confirmed_at) {
console.log("User created but not signed in, attempting automatic sign in...")
const signInResult = await supabase.auth.signInWithPassword({
email: userData.email.trim().toLowerCase(),
password: userData.password,
})
if (signInResult.data.session && signInResult.data.user) {
console.log("User successfully signed in after signup")
const authUser: AuthUser = {
id: signInResult.data.user.id,
email: signInResult.data.user.email!,
role: userData.role,
profile: {
firstName: userData.firstName,
lastName: userData.lastName,
companyName: userData.companyName,
phone: userData.phone,
},
}
return { success: true, user: authUser }
}
}
// If email confirmation is required
if (!data.user.email_confirmed_at) {
console.log("Email confirmation required")
return {
success: true,
user: null,
message: "Please check your email and click the confirmation link to activate your account.",
requiresConfirmation: true,
}
}
return { success: false, error: "Account created but sign-in failed. Please try signing in manually." }
}
return { success: false, error: "Account creation failed. Please try again." }
} catch (error: any) {
console.error(“Signup error:”, error)
return { success: false, error: `An unexpected error occurred: ${error.message || "Please try again."}` }
}
}
export async function signInWithEmail(email: string, password: string) {
try {
console.log(“Signing in user:”, email)
if (!email || !password) {
return { success: false, error: "Please enter both email and password." }
}
const { data, error } = await supabase.auth.signInWithPassword({
email: email.trim().toLowerCase(),
password: password,
})
if (error) {
console.error("Supabase signin error:", error)
// Provide user-friendly error messages
if (error.message.includes("Invalid login credentials")) {
return { success: false, error: "Invalid email or password. Please check your credentials." }
}
if (error.message.includes("Email not confirmed")) {
return { success: false, error: "Please check your email and confirm your account before signing in." }
}
if (error.message.includes("Too many requests")) {
return { success: false, error: "Too many login attempts. Please wait a moment and try again." }
}
return { success: false, error: `Sign in failed: ${error.message}` }
}
if (data.user && data.session) {
console.log("User signed in successfully:", data.user.id)
// Get user data from metadata first
const role = data.user.user_metadata?.role || "flipper"
const firstName = data.user.user_metadata?.first_name || "User"
const lastName = data.user.user_metadata?.last_name || ""
const companyName = data.user.user_metadata?.company_name
const phone = data.user.user_metadata?.phone
const authUser: AuthUser = {
id: data.user.id,
email: data.user.email!,
role: role as "flipper" | "contractor",
profile: {
firstName,
lastName,
companyName,
phone,
},
}
return { success: true, user: authUser }
}
return { success: false, error: "Sign in failed. Please try again." }
} catch (error: any) {
console.error(“Signin error:”, error)
return { success: false, error: `An unexpected error occurred: ${error.message || "Please try again."}` }
}
}
export async function signOut() {
try {
console.log(“Signing out user”)
const { error } = await supabase.auth.signOut()
if (error) {
throw error
}
console.log(“User signed out successfully”)
} catch (error: any) {
console.error(“Sign out error:”, error)
throw error
}
}
export function onAuthStateChange(callback: (user: AuthUser | null) => void) {
console.log(“Setting up auth state listener”)
return supabase.auth.onAuthStateChange(async (event, session) => {
console.log(“Auth state changed:”, event, session ? “with session” : “no session”)
if (session?.user) {
try {
const role = session.user.user_metadata?.role || "flipper"
const firstName = session.user.user_metadata?.first_name || "User"
const lastName = session.user.user_metadata?.last_name || ""
const companyName = session.user.user_metadata?.company_name
const phone = session.user.user_metadata?.phone
const authUser: AuthUser = {
id: session.user.id,
email: session.user.email!,
role: role as "flipper" | "contractor",
profile: {
firstName,
lastName,
companyName,
phone,
},
}
console.log("Auth user created:", authUser)
callback(authUser)
} catch (error) {
console.error("Error processing auth state change:", error)
callback(null)
}
} else {
console.log("No session, setting user to null")
callback(null)
}
}).data.subscription.unsubscribe
}
export async function getCurrentUser(): Promise<AuthUser | null> {
try {
const {
data: { session },
} = await supabase.auth.getSession()
if (!session?.user) {
return null
}
const role = session.user.user_metadata?.role || "flipper"
const firstName = session.user.user_metadata?.first_name || "User"
const lastName = session.user.user_metadata?.last_name || ""
const companyName = session.user.user_metadata?.company_name
const phone = session.user.user_metadata?.phone
return {
id: session.user.id,
email: session.user.email!,
role: role as "flipper" | "contractor",
profile: {
firstName,
lastName,
companyName,
phone,
},
}
} catch (error) {
console.error(“Error getting current user:”, error)
return null
}
}
middlewear.ts:
import { NextResponse } from “next/server”
import type { NextRequest } from “next/server”
const ALLOWLIST = (process.env.ALLOWED_ORIGINS || “”)
.split(“,”)
.map(s => s.trim())
.filter(Boolean)
export function middleware(request: NextRequest) {
const response = NextResponse.next()
// Security headers (fine to keep)
response.headers.set(“X-Frame-Options”, “DENY”)
response.headers.set(“X-Content-Type-Options”, “nosniff”)
response.headers.set(“Referrer-Policy”, “origin-when-cross-origin”)
response.headers.set(“X-XSS-Protection”, “1; mode=block”)
if (request.nextUrl.pathname.startsWith(“/api/”)) {
const origin = request.headers.get(“origin”) || “”
const allowOrigin =
ALLOWLIST.length === 0 ? “” :
ALLOWLIST.includes(origin) ? origin : “”
if (allowOrigin) {
response.headers.set("Vary", "Origin")
response.headers.set("Access-Control-Allow-Origin", allowOrigin)
response.headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
response.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization")
response.headers.set("Access-Control-Allow-Credentials", "true")
}
// Handle preflight
if (request.method === "OPTIONS") {
const preflight = new NextResponse(null, { status: 204 })
if (allowOrigin) {
preflight.headers.set("Vary", "Origin")
preflight.headers.set("Access-Control-Allow-Origin", allowOrigin)
preflight.headers.set("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,OPTIONS")
preflight.headers.set("Access-Control-Allow-Headers", "Content-Type, Authorization")
preflight.headers.set("Access-Control-Allow-Credentials", "true")
}
return preflight
}
}
return response
}
export const config = {
matcher: \[“/api/:path\*”, “/dashboard/:path\*”, “/admin/:path\*”\],
}