Next.js Discord

Discord Forum

Help! Next.js 15 with Supabase Auth (SSR) "Error: Invalid redirect arguments." for Google OAuth

Answered
Asiatic Lion posted this in #help-forum
Open in Discord
Avatar
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:
{
  "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";
View full answer

9 Replies

Avatar
Asiatic LionOP
app/login/oAuth.js
"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
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/.*).*)'
  ]
};
Avatar
why are you calling supabase/client with a "use server" in your oAuth
Avatar
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
Image
Avatar
Asiatic LionOP
my redirect import was incorrect, this is correct:
import { redirect } from "next/navigation";
Answer
Avatar
A few wrong imports, but yes that redirect and the import for supabase. Least you figured it out.