Supabase PKCE flow issue does not work all the time in deploy preview and production

Site name: account.vistamusic.com

I’m having some issue with the PKCE flow in production and deploy preview, it works for a few hours after a fresh build but it breaks afterwards until I deploy a new build.

The browser cookies supabase-{project ID}-auth-token and supabase-{project ID}-auth-token-verifier are being set and updated properly.

When it breaks, some issues start to happen:

  1. Session isn’t created or persisted, redirected user back to /login page due to protected page, the redirect to /error on the /auth/confirm route handler did not execute, so I’m assuming the verifyOtp() has been executed without any issue
  2. The email link works but it is served with data belonging to different users
  3. The supabase /verify request does not appear on the supabase authentication logs

All of my routes, api route handler are dynamic based on the info printed out by next build --debug, no Invalid or expired token logged on the supabase authentication logs.

// /auth/confirm route handler
import { NextResponse } from 'next/server';

import { supabaseServerClient } from '@/lib/supabase/supabaseServerClient';

export const dynamic = 'force-dynamic';
export async function GET(request) {
  const { searchParams } = new URL(request.url);
  const token_hash = searchParams.get('token_hash');
  const type = searchParams.get('type');
  const redirectUrl = searchParams.get('redirectUrl');
  const next = redirectUrl ? new URL(redirectUrl).pathname : '/';

  const redirectTo = request.nextUrl.clone();
  redirectTo.searchParams.delete('token_hash');
  redirectTo.searchParams.delete('type');
  redirectTo.searchParams.delete('redirectUrl');

  if (token_hash && type) {
    const supabase = await supabaseServerClient();
    const { error } = await supabase.auth.verifyOtp({
      type,
      token_hash,
    });

    if (error) {
      redirectTo.pathname = '/error';
      redirectTo.searchParams.set('code', error.status.toString());
      redirectTo.searchParams.set('message', error.message);
    } else {
      redirectTo.pathname = next;
    }

    return NextResponse.redirect(redirectTo);
  }

  redirectTo.pathname = '/error';
  redirectTo.searchParams.set('code', '422');
  redirectTo.searchParams.set('message', 'Missing OTP verification parameters in the link');
  return NextResponse.redirect(redirectTo);
}
// Middleware
import { NextResponse } from 'next/server';

import { createServerClient } from '@supabase/ssr';

const unprotectedRoutes = ['/login', '/signup', '/reset-password'];

export async function updateSession(request) {
  let response = NextResponse.next({
    request: {
      headers: request.headers,
    },
  });

  const supabase = createServerClient(process.env.NEXT_PUBLIC_SUPABASE_URL, process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY, {
    cookies: {
      getAll() {
        return request.cookies.getAll();
      },
      setAll(cookiesToSet) {
        cookiesToSet.forEach(({ name, value }) => request.cookies.set(name, value));
        response = NextResponse.next({ request });
        cookiesToSet.forEach(({ name, value, options }) => response.cookies.set(name, value, options));
      },
    },
  });

  const user = await supabase.auth.getUser();

  const isAuthApiRoute = request.nextUrl.pathname.startsWith('/auth');
  const isUnprotectedRoute = unprotectedRoutes.some((route) => request.nextUrl.pathname.startsWith(route));

  // When the user is logged in but tries to access public route
  if (!user.error && !isAuthApiRoute && isUnprotectedRoute) {
    return NextResponse.redirect(new URL('/', request.url));
  }
 
  return response;
}
// I'm using this function as a data access layer to protect server actions and pages
import { cache } from 'react';

import { supabaseServerClient } from '@/lib/supabase/supabaseServerClient';

export const verifySession = cache(async function () {
  const supabase = await supabaseServerClient();
  const { data, error } = await supabase.auth.getUser();

  if (error) {
    console.info(
      `Error retrieving current user details; Code: ${error.code}, name: ${error.name}, message: ${error.message}`
    );
  }

  return error || !data?.user
    ? null
    : {
        email: data.user.email,
        id: data.user.id,
      };
});

This seems to be a duplicate of a helpdesk ticket we are already working on. Please continue the conversation there.