Help! Next.js 15 with Supabase Auth (SSR) "Error: Invalid redirect arguments." for Google OAuth
Answered
Asiatic Lion posted this in #help-forum
Asiatic LionOP
When I click to sign in, I get "Error: Invalid redirect arguments." But if I console log the url and enter it, everything seems to be correct. I have tried to implement it for days to no avail. Please help me. Files are listed below:
package.json:
app/login/page.js:
package.json:
{
"name": "aja-broneerimine",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev --turbo",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@prisma/client": "^5.22.0",
"@supabase/ssr": "^0.5.2",
"@supabase/supabase-js": "^2.46.1",
"next": "^15.0.3",
"react": "19.0.0-rc-65a56d0e-20241020",
"react-datepicker": "^7.5.0",
"react-dom": "19.0.0-rc-65a56d0e-20241020",
"react-select": "^5.8.2"
},
"devDependencies": {
"typescript": "5.6.3"
}
}
app/login/page.js:
"use client";
import styles from "@/styles/login.module.css"
import { useState } from "react";
import { signInWithGoogle } from "./oAuth";
export default function LoginPage() {
const [showMoreOptions, setShowMoreOptions] = useState(false);
return (
<div className={styles.body}>
<div className={styles.loginContainer}>
<div className={styles.topContainer}>
<div className={styles.loginOptions}>
<button className={styles.loginButton} onClick={signInWithGoogle}>Google</button>
<button
className={styles.moreOptionsButton}
onClick={() => setShowMoreOptions(!showMoreOptions)}
>
Kasutajatunnusega sisselogimine{showMoreOptions ? " ▲" : " ▼"}
</button>
{showMoreOptions && (
// code left out due to character limit
)}
</div>
</div>
</div>
</div>
);
}
Answered by Asiatic Lion
my redirect import was incorrect, this is correct:
import { redirect } from "next/navigation";
9 Replies
Asiatic LionOP
app/login/oAuth.js
app/utils/supabase/server.ts
"use server"
import { redirect } from "next/dist/server/api-utils";
import { createClient } from "../utils/supabase/client";
import { headers } from "next/headers"
export async function signInWithGoogle() {
const supabase = await createClient();
const origin = headers().get("origin");
const { error, data } = await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${origin}/auth/callback`,
}
});
if (error) {
console.log(error);
} else {
return redirect(data.url);
}
}
app/utils/supabase/server.ts
import { createServerClient, type CookieOptions } from '@supabase/ssr'
import { cookies } from 'next/headers';
export const createClient = async () => {
const cookieStore = cookies();
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
{
cookies: {
getAll() {
return cookieStore.getAll()
},
setAll(cookiesToSet) {
try {
cookiesToSet.forEach(({ name, value, options }) =>
cookieStore.set(name, value, options)
)
} catch {
}
},
},
}
)
}
app/utils/supabase/middleware.ts
app/utils/supabase/client.ts
import "server-only"
import { createServerClient } from '@supabase/ssr'
import { NextResponse, type NextRequest } from 'next/server'
export async function updateSession(request: NextRequest) {
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)
)
},
},
}
)
const {
data: { user },
} = await supabase.auth.getUser()
if (
!user &&
!request.nextUrl.pathname.startsWith('/login') &&
!request.nextUrl.pathname.startsWith('/auth')
) {
const url = request.nextUrl.clone()
url.pathname = '/login'
return NextResponse.redirect(url)
}
return supabaseResponse
}
app/utils/supabase/client.ts
import { createBrowserClient } from '@supabase/ssr'
export function createClient() {
return createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
)
}
app/auth/callback/route.ts
import { NextResponse } from 'next/server'
// The client you created from the Server-Side Auth instructions
import { createClient } from '../../utils/supabase/server'
export async function GET(request: Request) {
const { searchParams, origin } = new URL(request.url)
const code = searchParams.get('code')
// if "next" is in param, use it as the redirect URL
const next = searchParams.get('next') ?? '/'
if (code) {
const supabase = await createClient()
await supabase.auth.exchangeCodeForSession(code)
}
// return the user to an error page with instructions
return NextResponse.redirect(origin)
}
middleware.ts
import { createServerClient } from '@supabase/ssr';
import { NextResponse, type NextRequest } from 'next/server';
export async function middleware(request: NextRequest) {
// Initialize Supabase client and handle session
let response = 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 }) =>
request.cookies.set(name, value)
);
response = NextResponse.next({
request
});
cookiesToSet.forEach(({ name, value, options }) =>
response.cookies.set(name, value, options)
);
}
}
}
);
const {
data: { user: session }
} = await supabase.auth.getUser();
// Handle route-specific redirects
const currentRoute = request.nextUrl.pathname;
if (currentRoute.startsWith('/protected') && !session) {
const redirectUrl = new URL(request.url);
redirectUrl.pathname = '/auth/signin';
return NextResponse.redirect(redirectUrl);
}
if (currentRoute.startsWith('/actionchat') && !session) {
const redirectUrl = new URL(request.url);
redirectUrl.pathname = '/auth/signin';
return NextResponse.redirect(redirectUrl);
}
if (currentRoute.startsWith('/aichat') && !session) {
const redirectUrl = new URL(request.url);
redirectUrl.pathname = '/auth/signin';
return NextResponse.redirect(redirectUrl);
}
return response;
}
export const config = {
matcher: [
'/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp|css|js|json|md|mdx|pdf|doc|docx|xls|xlsx|ppt|pptx|zip|tar|gz|rar|mp3|wav|ogg|flac|mp4|avi|mov|wmv|flv)$|api/.*|fonts/.*|sitemap.xml|robots.txt|manifest.json|\\.well-known/.*).*)'
]
};
why are you calling supabase/client with a "use server" in your oAuth
Asiatic LionOP
good question. i have tried so many different things and haven't gotten rid of some things
if i remove it i get this
Asiatic LionOP
my redirect import was incorrect, this is correct:
import { redirect } from "next/navigation";
Answer
A few wrong imports, but yes that redirect and the import for supabase. Least you figured it out.