2024-09-09 web, development, javascript
Supabase Auth REST API with Next.js
By O. Wolfson
To create a persistent auth using the Supabase REST API with Next.js and the App Router, you can follow these general steps:
-
Create a login page component that allows users to enter their email and password.
-
When the user submits the login form, send a request to the Supabase
session
endpoint to create a new session for the user. -
If the login is successful, store the
refresh_token
securely on the client side using a secure storage mechanism such as an HTTP-only cookie orlocalStorage
. -
Create a higher-order component (HOC) that wraps your protected pages and checks for the presence of a valid
access_token
before rendering the protected page. -
If the
access_token
is not present or has expired, use therefresh_token
to obtain a newaccess_token
from the Supabasetoken
endpoint. -
If the
refresh_token
is invalid or has expired, redirect the user to the login page. -
When the user logs out, delete the
refresh_token
from the client-side storage.
js// pages/login.tsx
import { useState } from "react";
import { useRouter } from "next/router";
import { createSession } from "../lib/supabase";
export default function Login() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
const { error } = await createSession(email, password);
if (error) {
console.error(error);
} else {
router.push("/");
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<button type="submit">Log in</button>
</form>
);
}
In this example, we define a Login
component that allows users to enter their email and password. When the user submits the login form, we call the createSession
function to create a new session for the user. If the login is successful, we use the useRouter
hook to navigate to the home page.
js// lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
export const createSession = async (email, password) => {
const { error } = await supabase.auth.signIn({ email, password });
return { error };
};
export const refreshAccessToken = async (refreshToken) => {
const { data, error } = await supabase.auth.api.refreshAccessToken(
refreshToken
);
return { data, error };
};
In this example, we define a supabase
client object using the createClient
function from the @supabase/supabase-js
library. We also define a createSession
function that calls the signIn
method on the auth
object to create a new session for the user. Finally, we define a refreshAccessToken
function that calls the refreshAccessToken
method on the auth.api
object to obtain a new access_token
using a refresh_token
.
js// lib/withAuth.tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
import { supabase, refreshAccessToken } from "./supabase";
const withAuth = (Component) => {
const AuthComponent = (props) => {
const router = useRouter();
useEffect(() => {
const accessToken = supabase.auth.session()?.access_token;
const refreshToken = localStorage.getItem("refresh_token");
if (!accessToken && !refreshToken) {
router.push("/login");
} else if (!accessToken && refreshToken) {
refreshAccessToken(refreshToken).then(({ data, error }) => {
if (error) {
console.error(error);
localStorage.removeItem("refresh_token");
router.push("/login");
} else {
supabase.auth.setSession(data);
}
});
}
}, []);
return <Component {...props} />;
};
return AuthComponent;
};
export default withAuth;
In this example, we define a higher-order component (HOC) called withAuth
that wraps our protected pages and checks for the presence of a valid access_token
before rendering the protected page. If the access_token
is not present or has expired, the HOC uses the refresh_token
to obtain a new access_token
from the Supabase token
endpoint. If the refresh_token
is invalid or has expired, the HOC redirects the user to the login page.
To use the withAuth
HOC, you can wrap your protected pages like this:
js// pages/dashboard.tsx
import withAuth from "../lib/withAuth";
const Dashboard = () => {
return (
<div>
<h1>Dashboard</h1>
<p>This is a protected page.</p>
</div>
);
};
export default withAuth(Dashboard);
In this example, we define a Dashboard
component that is wrapped with the withAuth
HOC. This ensures that the Dashboard component is only rendered if the user has a valid access_token
.
Finally, when the user logs out, you can delete the refresh_token
from the client-side storage like this:
js// pages/logout.tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
export default function Logout() {
const router = useRouter();
useEffect(() => {
localStorage.removeItem("refresh_token");
router.push("/login");
}, []);
return null;
}
In this example, we define a Logout
component that removes the refresh_token
from the client-side storage and navigates the user to the login page.
Supabase Auth REST API Sign up
To create a sign-up page using the Supabase REST API, you can follow these general steps:
-
Create a sign-up page component that allows users to enter their email and password.
-
When the user submits the sign-up form, send a request to the Supabase
auth
endpoint to create a new user. -
If the sign-up is successful, store the
refresh_token
securely on the client side using a secure storage mechanism such as an HTTP-only cookie orlocalStorage
. -
Redirect the user to the home page or a confirmation page.
Here's an example of how you could implement these steps using Next.js and the App Router:
js// pages/signup.tsx
import { useState } from "react";
import { useRouter } from "next/router";
import { supabase } from "../lib/supabase";
export default function Signup() {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const router = useRouter();
const handleSubmit = async (event) => {
event.preventDefault();
const { error } = await supabase.auth.signUp({ email, password });
if (error) {
console.error(error);
} else {
router.push("/");
}
};
return (
<form onSubmit={handleSubmit}>
<label>
Email:
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>
</label>
<label>
Password:
<input
type="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
/>
</label>
<button type="submit">Sign up</button>
</form>
);
}
In this example, we define a Signup
component that allows users to enter their email and password. When the user submits the sign-up form, we call the signUp
function to create a new user account. If the sign-up is successful, we use the useRouter
hook to navigate to the home page.
js// lib/supabase.ts
import { createClient } from "@supabase/supabase-js";
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
export const supabase = createClient(supabaseUrl, supabaseAnonKey);
export const refreshAccessToken = async (refreshToken) => {
const { data, error } = await supabase.auth.api.refreshAccessToken(
refreshToken
);
return { data, error };
};
In this example, we define a supabase
client object using the createClient
function from the @supabase/supabase-js
library. We also define a refreshAccessToken
function that calls the refreshAccessToken
method on the auth.api
object to obtain a new access_token
using a refresh_token
.
Finally, when the user logs out, you can delete the refresh_token
from the client-side storage like this:
js// pages/logout.tsx
import { useEffect } from "react";
import { useRouter } from "next/router";
export default function Logout() {
const router = useRouter();
useEffect(() => {
localStorage.removeItem("refresh_token");
router.push("/login");
}, []);
return null;
}
In this example, we define a Logout
component that removes the refresh_token
from the client-side storage and navigates the user to the login page.