Blog

Google Authentication with NextJS 14 Server Actions and Supabase Guide

29 Apr 2025
By Denis

Google Authentication with NextJS and Supabase

Authentication is a crucial part of any SaaS. If a few years ago it was a nightmare to implement, now it's much easier with the help of services like Supabase. In this guide, we will learn how to configure Google Authentication with NextJS and Supabase.

If you are struggling to implement Google Authentication yourself, check out SupaLaunch — Next.JS & Supabase starter kit. It has everything you need to start building your SaaS: authentication, database, payments, file storage, emails, SEO, landing page, and more.

Configuring Authentication in Google Cloud and Supabase

Let us start with configuring Google Authentication in Google Cloud and in Supabase. When everything is configured, we can start implementing the authentication flow in our NextJS app.

Authentication in Google Cloud consists of two parts: setting up the OAuth consent screen and creating OAuth credentials. We will use these credentials to authenticate users in our NextJS app.

  1. Go to the Google Cloud and create a new project if you don't have one already.

  2. Go to OAuth consent screen in Google Cloud to create a new consent screen. This is the screen that users will see when they log in to your app using Google.

    • [OAuth consent screen] Set up the consent screen settings

      • User Type: External
      • Application name/logo: anything you want
      • Add your website URL, privacy policy and terms of service URLs. It is important that you have both of these pages on your website.
      • Add your domain as an authorized domain. You can add your domain to Google Search Console to verify it.
      • Add your email address
    • [Scopes] Add the following scopes to Your non-sensitive scopes:

      • .../auth/userinfo.email
      • .../auth/userinfo.profile
    • [Test users] section

      • Add your email address as a test user
    • [Summary] section

      • Check if everything is correct. If yes, click Back to Dashboard button.
  3. Now let's configure Credentials. Go to Credentials and click Create Credentials to create a new OAuth Client ID.

    • Application type: Web application
    • Name: anything you want
    • Authorized JavaScript origins: leave empty
    • Authorized redirect URIs: add http://localhost:54321/auth/v1/callback for local development and https://<project-id>.supabase.co/auth/v1/callback for production (replace <project-id> with your Supabase project ID).
    • Click Create button
    • Save Client ID and Client Secret for later

Now let's turn on Google Authentication in Supabase. Go to Supabase -> Authentication -> Providers.

Supabase Authentication Providers

Turn on Google Authentication and add your Client ID and Client Secret from Google Cloud.

Make sure that Callback URL (for OAuth) from Supabase matches the Authorized redirect URIs from Google Cloud.

Click Save button. Now Google Authentication is enabled in your Supabase project in test mode. We will need to get our app approved by Google to use it in production. But we will do it later when our app is ready. For now, we can use it in test mode.

Implementing Google Authentication in NextJS

Now let's implement Google Authentication in our NextJS app. We will be using next.js server actions to handle the authentication flow. If you are not familiar with server actions, check our guide on how to create forms with Next.JS 14 Server Actions.

Authentication Page and React Component

First, we start with the authentication page and React component. We will use tailwindcss and daisyUI for styling. Create a new file @/app/auth/auth-form.tsx:

'use client'

import {login} from "@/app/auth/actions";

export default function AuthForm() {
    return (
            <div className="card-body">
                <h2 className="card-title">Login</h2>
                    <div>
                        <div className="p-5 col-6">
                            <form className='flex justify-center p-5'>
                                <button
                                    className='btn btn-primary w-full px-10'
                                    formAction={login}
                                >
                                    <img src="/images/google_logo.png" alt="google" className="w-6 h-6"/>
                                    Login with Google
                                </button>
                            </form>
                        </div>
                    </div>
            </div>
    )
}

Now let's create a server action (note 'use server' on the top of the file) to handle authentication. Create a new file @/app/auth/actions.ts:

'use server'

import { revalidatePath } from 'next/cache'
import { redirect } from 'next/navigation'
import { createClient } from "@/lib/utils/supabase/server"; // createClient is a function to create a Supabase client

export async function login(formData: FormData) {
    const supabase = createClient()
    let redirectURL = `${process?.env?.NEXT_PUBLIC_SITE_URL}api/auth/callback`

    const {data, error} = await supabase.auth.signInWithOAuth({
        provider: 'google',
        options: {
            redirectTo: redirectURL,
            scopes: 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile',
        },
    })

    if (error) {
        console.log('error', error)
        redirect('/auth/error')
    } else {
        revalidatePath('/', 'layout')
        redirect(data.url)
    }
}

Note two things in the code above:

  1. We are using createClient function to create a Supabase client. This function should be implemented in @/lib/utils/supabase/server.ts file. Check the Supabase documentation on how to create a Supabase client for server component.
  2. We create a redirect URL to /api/auth/callback route. We will implement this route later. But you need to add NEXT_PUBLIC_SITE_URL=your-site-url environment variable to your .env.local file. Use http://localhost:3000 for local development. For production, use your production URL.

Now let's create a login page. Create a new file @/pages/auth/login/page.tsx:

import AuthForm from "@/app/auth/auth-form";


export default function Login() {
    return (
        <div className="flex items-center justify-center h-screen">
            <div className="card bg-base-200 shadow-xl max-w-md">
                <AuthForm/>
            </div>
        </div>
    )
}

Now you can navigate to /auth/login in your browser and see the login form.

Login with Google

Cool! The login form is ready. Now let's implement the authentication callback route.

Authentication Callback Route

Create a new file @/app/api/auth/callback/route.ts:

import {cookies} from 'next/headers'
import {NextResponse} from 'next/server'
import {type CookieOptions, createServerClient} from '@supabase/ssr'
import {getSession} from "@/lib/db/server-side/user";
import {supabaseAdmin} from "@/lib/db/server-side/supabase-admin";
import {createContact, sendEmail} from "@/lib/emails/send-email";
import {getWelcomeEmail} from "@/lib/emails/email-templates";

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') ?? '/sites'

    if (code) {
        const cookieStore = cookies()
        const supabase = createServerClient(
            process.env.NEXT_PUBLIC_SUPABASE_URL!,
            process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
            {
                cookies: {
                    get(name: string) {
                        return cookieStore.get(name)?.value
                    },
                    set(name: string, value: string, options: CookieOptions) {
                        cookieStore.set({name, value, ...options})
                    },
                    remove(name: string, options: CookieOptions) {
                        cookieStore.delete({name, ...options})
                    },
                },
            }
        )
        const {error} = await supabase.auth.exchangeCodeForSession(code)
        if (!error) {
            const session = await getSession()

            const {error: tokenError, statusText, status} = await supabaseAdmin
                .from('google')
                .upsert({
                    user_id: session.user.id,
                    provider_token: session.provider_token,
                    provider_refresh_token: session.provider_refresh_token,
                })

            if (tokenError) {
                console.error('tokenError', tokenError)
            }

            // check if this is a signup or a login
            if (statusText === 'Created') {
                await createContact({
                    email: session.user.email,
                    firstName: session.user.user_metadata['name'],
                })
                await sendEmail(session.user.email, getWelcomeEmail(session.user.user_metadata['name']))
            }
            return NextResponse.redirect(`${origin}${next}`)
        }
    }

    // return the user to an error page with instructions
    return NextResponse.redirect(`${origin}/auth/auth-code-error`)
}


Whenever you are ready with your app, return to the OAuth consent screen and click Publish App button. This will start the process of getting your app approved by Google. If it shows Prepare for verification button, go through the steps required.

Google will email you asking about the status of your app. Reply to this email.

:::info Usually, it took me a couple of days to get my app approved by Google. But it can take longer if you use more sensitive scopes. :::

import {createClientComponentClient} from '@supabase/auth-helpers-nextjs'

const supabase = createClientComponentClient()

Add the following environment variables to your .env.local file:

NEXT_PUBLIC_SUPABASE_URL=
NEXT_PUBLIC_SUPABASE_ANON_KEY=
SUPABASE_SERVICE_ROLE_KEY=

Y

const {data, error} = await supabase.auth.signInWithOAuth({
    provider: 'google',
    options: {
        redirectTo: redirectURL,
    },
})
if (error) {
    setError(error.message)
} else {
    setError(null)
}

API Route to handle the authentication callback

/api/auth/callback/route.ts

import { createRouteHandlerClient } from '@supabase/auth-helpers-nextjs'
import { cookies } from 'next/headers'
import { NextRequest, NextResponse } from 'next/server'

export const dynamic = 'force-dynamic'

export async function GET(req: NextRequest) {
    const supabase = createRouteHandlerClient({ cookies })
    const { searchParams } = new URL(req.url)
    const code = searchParams.get('code')

    if (code) {
        await supabase.auth.exchangeCodeForSession(code)
    }

    return NextResponse.redirect(new URL('/', req.url))
}

Launch your SaaS with our Supabase + NextJS Course

Learn how to create an AI-powered SaaS from scratch using Supabase and Next.js. We will guide you from zero to launch. What you will learn:

  • Stripe payments
  • Authentication with email/password and social logins
  • AI functionality: Chat, Langchain integration, OpenAI API streaming and more
  • How to setup emails, file storage, etc.