Supabase and Next.js 14 - Authentication and Protected Routes
Authentication with Email and Social Login using new @supabase/ssr package and Next.js 14 App Router
Hi, in this topic we will connect a Next.JS app with Supabase and set up authentication flow step by step.
While there is a detailed blog in Supabase documentation, that documentation uses @supabase/auth-helpers-nextjs. However, in recent documentation, Supabase recommends using the @supabase/ssr package. Related documentation can be found at the end of the writing.
Next.js
Next.js is a modern framework that is built in React. It handles most of the work out of the box which requires manual handling in React. One of the best features of Next.js is server-side components. With server-side components (SSR), a page can be rendered on the server and the client only sees the page in HTML without loading extra Javascript dependencies. Therefore clients get better and faster results.
More information about Next.js can be found on the official site: Next.js
Supabase
Supabase is an open-source project alternative to Firebase. However, it offers more features and capabilities. Using Supabase; authentication, database management, objects, and storage can be used in a single application. Supabase integrates many components to work as one. In the backend, it uses PostgreSQL as a database.
More information about Supabase can be found on the official site: Supabase
Setup Flow by Steps
We will create our Next.js app and supabase account, add necessary packages, and create an authentication flow.
I will use yarn to manage node packages. If you like you can use npm or other package managers.
Create a New Next.js App
yarn create next-app
This will create a new application. I will use JavaScript in this tutorial for simplicity. The same steps can be used with TypeScript too. I selected the default options and continued.
Create a Supabase Account and Project
Supabase is an open-source project, which means it can be run on a local or personal server. But Supabase offers a free plan, in this tutorial we will use this option.
Go to supabase.com and create an account. After that go to Dashboard | Supabase. On this page create an organization if not exist and create a project. It will take a while to create the project.
Set Next.js env with Supabase Credentials
Go to your Next.js project root folder and create a file named .env.
Then, go to Supabase project settings and copy them to the .env file. You can find the project URL and anon key in project settings in the API tab.
NEXT_PUBLIC_SUPABASE_URL=your-project-url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your-anon-key
Add Dependencies to Project
yarn add @supabase/supabase-js @supabase/ssr
Create Helper Functions using Supabase Auth
Supabase SSR package comes with two different functions to handle authentication on the server side and client side. To prevent code duplication and aiming code simplicity we will create 3 different create client functions to access Supabase. For server-side, client-side, and middleware usage.
Create a lib/supabase folder inside the src folder and create 3 files named: client, middleware, and server.
Server
import {createServerClient} from "@supabase/ssr";
export const createClient = (cookieStore) => {
return createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
get(name) {
return cookieStore.get(name)?.value;
},
set(name, value, options) {
try {
cookieStore.set({name, value, ...options});
} catch (error) {
}
},
remove(name, options) {
try {
cookieStore.set({name, value: "", ...options});
} catch (error) {
}
},
},
},
);
};
Client
import {createBrowserClient} from "@supabase/ssr";
export const createClient = () =>
createBrowserClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
);
Middleware
import {createServerClient} from "@supabase/ssr";
import {NextResponse} from "next/server";
export const createClient = (request) => {
let response = NextResponse.next({
request: {
headers: request.headers,
},
});
const supabase = createServerClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
{
cookies: {
get(name) {
return request.cookies.get(name)?.value;
},
set(name, value, options) {
request.cookies.set({
name,
value,
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value,
...options,
});
},
remove(name, options) {
request.cookies.set({
name,
value: "",
...options,
});
response = NextResponse.next({
request: {
headers: request.headers,
},
});
response.cookies.set({
name,
value: "",
...options,
});
},
},
},
);
return {supabase, response};
};
Authentication
Now we can use our helper function to authenticate flow
Create Middleware for Refresh Tokens
Create a middleware.js file in the root folder
import {createClient} from "@/lib/supabase/middleware";
export async function middleware(req) {
const {supabase, response} = createClient(req);
await supabase.auth.getSession();
return response;
}
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).*)",
],
};
Create A Route Handler for Authorization Code Flow
Create a file route.js in src/app/auth/callback. This route will be called by login or signup to exchange code with the token.
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
import { cookies } from "next/headers";
export async function GET(request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const cookieStore = cookies();
const supabase = createClient(cookieStore);
await supabase.auth.exchangeCodeForSession(code);
}
return NextResponse.redirect(requestUrl.origin);
}
Sign in and Sign up
You can use Supabase methods for these operations.
If the component is client-side, then
import {createClient} from '@/lib/supabase/client';
And if in the server side or a route handler
import {createClient} from '@/lib/supabase/server';
Now I will give a sample login and signup components using client-side components.
'use client';
import {createClient} from '@/lib/supabase/client';
import {useRouter} from 'next/navigation';
import {useState} from 'react';
export default function Login() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const supabase = createClient();
const handleSignIn = async () => {
const {error} = await supabase.auth.signInWithPassword({
email,
password,
});
if (error) {
console.log(error);
}
return router.push("/");
};
return (
<main>
{/* form component to get email and password */}
</main>
);
}
'use client';
import {createClient} from '@/lib/supabase/client';
import {useRouter} from 'next/navigation';
import {useState} from 'react';
export default function Signup() {
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const router = useRouter();
const supabase = createClient();
const handleSignUp = async () => {
const {error} = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: `${location.origin}/auth/callback`,
},
});
if (error) {
console.log(error);
}
router.push("/");
};
return (
<main>
{/* form component to get email and password */}
</main>
);
}
Social Login (Google, Facebook, etc.)
Most of the users use social login options instead of writing email and password every time. With Supabase we can configure and use social login options. I will show the login with Google. Supabase has documentation for all login options.
First of all, go to Google Cloud Platform and create a project if not exist. Then visit the OAuth Consent Screen page. On this page, you will configure to give access to Supabase. On authorized domains add your Supabase project's domain <project-id>.supabase.co and set the scope to: .../auth/userinfo.email, .../auth/userinfo.profile, openid. After that save changes.
Go to Credentials. Select Create Credentials and OAuth client ID. Choose the web application as the application type and add https://<project-id>.supabase.co/auth/v1/callback as authorized redirect URIs.
Create and copy client ID and client secret. Go to Supabase Dashboard>Authentication>Auth Scopes and enable Google copy the client ID and secret here.
Now you can use Google to sign in users.
const handleGoogleLogin = async () => {
await supabase.auth.signInWithOAuth({
provider: "google",
options: {
redirectTo: `${location.origin}/auth/callback`,
},
});
return router.push("/");
};
Protect Pages with Authentication
To prevent access to a page without authentication supabase session should be checked.
import {createClient} from "@/lib/supabase/server";
import { cookies } from "next/headers";
import {redirect} from "next/navigation";
export default async function Home() {
const supabase = createClient(cookies());
const {data: {session}} = await supabase.auth.getSession();
if (!session) {
redirect('/login')
}
return (
<main className="flex min-h-screen flex-col items-center justify-between p-24">
Hello World with Authenticated Page
</main>
);
}
Thank you for your time and for reading my blog. If you have any questions feel free to ask in the comments.
For more content follow my blog and personal website ekremsonmezer.com.
Resources
https://nextjs.org/
https://supabase.com/
https://supabase.com/docs/guides/auth/auth-helpers/nextjs
https://supabase.com/docs/guides/auth/quickstarts/nextjs
https://supabase.com/docs/guides/auth/social-login
https://supabase.com/docs/guides/auth/social-login/auth-google
https://github.com/dijonmusters/nextjs-auth-helpers-videos