Skip to content

Instantly share code, notes, and snippets.

@Gbillington1
Created August 28, 2024 19:36
Show Gist options
  • Save Gbillington1/97135ec16aab7863ff71aa535d7970d9 to your computer and use it in GitHub Desktop.
Save Gbillington1/97135ec16aab7863ff71aa535d7970d9 to your computer and use it in GitHub Desktop.
Supabase JS 2.42.2 Error with exchangeCodeForSession in PKCE Flow
// ./src/middleware.js
import { updateSession } from "@/src/libs/supabase/middleware";
export async function middleware(request) {
return await updateSession(request);
}
export const config = {
matcher: [
/*
* Match all request paths except for the ones starting with:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
* Feel free to modify this pattern to include more paths.
*/
'/((?!_next/static|_next/image|favicon.ico|icon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)',
],
};
// ./src/signin/page.js
// This is where the OAuth flow is started. I want to use PKCE so I can pass the user type through to supabase, so I can use it in a trigger
"use client";
import Link from "next/link";
import { useState } from "react";
import config from "@/src/config";
import { createClient } from "@/src/libs/supabase/client";
import { USER_ROLE } from "@/src/libs/constants";
import toast from "react-hot-toast";
import { signUpWithOAuth } from "../actions";
import { set } from "zod";
// This a login/singup page for Supabase Auth.
// Successfull login redirects to /api/auth/callback where the Code Exchange is processed (see app/api/auth/callback/route.js).
export default function Login() {
const [isLoading, setIsLoading] = useState(false);
console.log(process.env.NODE_ENV)
const handleSignup = async (e, options) => {
e?.preventDefault();
setIsLoading(true);
// ignore, this is the server actions implementations (yields same error)
// const redirectURL = window.location.origin + "/api/auth/callback";
// try {
// await signUpWithOAuth("google", redirectURL);
// } catch (error) {
// toast.error("Failed to sign-up with Google");
// } finally {
// setIsLoading(false);
// }
try {
const { type, provider } = options;
const redirectURL = window.location.origin + "/api/auth/callback";
if (type === "oauth") {
const supabase = createClient();
await supabase.auth.signInWithOAuth({
provider,
options: {
redirectTo: redirectURL,
data: {
role: USER_ROLE.USER.id,
}
},
});
}
} catch (error) {
console.log(error);
} finally {
setIsLoading(false);
}
};
return (
<main className="p-8 md:p-24" data-theme={config.colors.theme}>
// handle sign up event happens here
<button
className="btn btn-block"
onClick={(e) =>
handleSignup(e, { type: "oauth", provider: "google" })
}
disabled={isLoading}
>
Sign-up with Google
</button>
</main>
);
}
// ./src/app/api/auth/callback/route.js
import { NextResponse } from "next/server";
import config from "@/src/config";
import { createClient } from "@/src/libs/supabase/server";
export const dynamic = "force-dynamic";
// This route is called after a successful login. It exchanges the code for a session and redirects to the callback URL (see config.js).
export async function GET(req) {
const requestUrl = new URL(req.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const supabase = createClient();
const { error } = await supabase.auth.exchangeCodeForSession(code); // THIS IS WHERE THE ERROR HAPPENS
if (error) {
console.error("Error exchanging code for session", error);
return NextResponse.error(new Error("Error exchanging code for session"));
}
}
// URL to redirect to after sign in process completes
return NextResponse.redirect(requestUrl.origin + config.auth.callbackUrl);
}
// ./src/libs/supabase/middleware.js
import { createServerClient } from "@supabase/ssr";
import { NextResponse } from "next/server";
export async function updateSession(request) {
let supabaseResponse = NextResponse.next({
request,
});
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, options }) => request.cookies.set(name, value))
supabaseResponse = NextResponse.next({
request,
})
cookiesToSet.forEach(({ name, value, options }) =>
supabaseResponse.cookies.set(name, value, options)
)
},
},
}
)
// IMPORTANT: Avoid writing any logic between createServerClient and
// supabase.auth.getUser(). A simple mistake could make it very hard to debug
// issues with users being randomly logged out.
const { data: { user } } = await supabase.auth.getUser()
console.log("MIDDLEWARE USER", user) // prints NULL always
// IMPORTANT: You *must* return the supabaseResponse object as it is. If you're
// creating a new response object with NextResponse.next() make sure to:
// 1. Pass the request in it, like so:
// const myNewResponse = NextResponse.next({ request })
// 2. Copy over the cookies, like so:
// myNewResponse.cookies.setAll(supabaseResponse.cookies.getAll())
// 3. Change the myNewResponse object to fit your needs, but avoid changing
// the cookies!
// 4. Finally:
// return myNewResponse
// If this is not done, you may be causing the browser and server to go out
// of sync and terminate the user's session prematurely!
return supabaseResponse
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment