Supabase Auth Deployment Error

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\*”\],
}

Did you know there’s another category dedicated to v0 topics? A human should be here soon to help, but the answer may already be available even faster in one of these other posts.

Our docs are a great place to start.

We also have a walkthrough to help guide your workflow.

And these recordings can give you a look at v0 features and strategies in action shown by our Community:

https://community.vercel.com/tags/c/events/42/v0

Hi @louisdsinger-2412, welcome to the Vercel Community!

Can you share the chat URL or the code base so community members can take a look and try to help you?

sure just shared it would love some help!

Hi @louisdsinger-2412, I wasn’t able to test it out myself but I quickly went and asked v0 on how to fix this:

https://v0.app/chat/new-chat-oeeQqZZNwZl

Can you try this solution and see if it makes sense?

It makes sense, but i’ve done that same thing with v0 for hours and it still does not work.