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.
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.
Go to the Google Cloud and create a new project if you don't have one already.
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
[Scopes] Add the following scopes to Your non-sensitive scopes:
.../auth/userinfo.email
.../auth/userinfo.profile
[Test users] section
[Summary] section
Back to Dashboard
button.Now let's configure Credentials
. Go to Credentials and click Create Credentials
to create a new OAuth Client ID.
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).Create
buttonNow let's turn on Google Authentication in Supabase. Go to 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.
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.
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:
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./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.
Cool! The login form is ready. Now let's implement the 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/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))
}
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: