Under the Hood
Next.js is the best framework for building almost all categories of web applications. In today's section, we will again recall adding Authentication in a Next.js application.
The following options are available for adding Authentication:
- Firebase Authentication
- Supabase Authentication
- Appwrite Authentication
- Authentication using Clerk
We have so many options to add authentication to the frontend website. Most of them help in session management, store management, OAuth user credentials and more.
But it's important to recall the options on the table before finalising the one of your choice.
Third-party Authentication
Every third-party authentication follows a step process along with a few lines of code execution to integrate authentication within the application.
For example, Firebase provides an admin dashboard for allowing each authentication provider from the options of Google, Github, Twitter, Slack, etc, which is then integrated within the application using a few lines of code.
This admin dashboard is the same for all kinds of third-party authentication such as Firebase, Supabase, Appwrite and Clerk.
Admin dashboards are quite common visual interaction websites to view and manage the authenticated users of each website. Since it's a dashboard by default, it will serve the purpose of multi-project management in itself. Each of the platforms, Firebase, Supabase and Appwrite or Clerk or any other in the future, will provide a dashboard for the same. Maybe in future we will have one interface to manage multiple authentication options from one single interface.
Initialization
Authentication or a serverless database usually needs initialisation at the time of starting the web server or running the client locally.
Firebase, Supabase, all need a way to initialise the npm module.
import { initializeApp } from "firebase/app";
import { getAuth } from "firebase/auth";
const firebaseConfig = {
apiKey: "YOUR_API_KEY",
authDomain: "your-project.firebaseapp.com",
projectId: "your-project-id",
appId: "your-app-id",
};
const app = initializeApp(firebaseConfig);
export const auth = getAuth(app);Supabase
import { createClient } from '@supabase/supabase-js';
export const supabase = createClient(
'https://your-project.supabase.co',
'public-anon-key'
);Clerk
import { Clerk } from '@clerk/clerk-sdk-node';
const clerk = new Clerk({ secretKey: 'YOUR_CLERK_SECRET_KEY' });
export default clerk;Auth using Email and Signup
Firebase auth
import { createUserWithEmailAndPassword, signInWithEmailAndPassword } from 'firebase/auth';
export async function loginOrSignup(email: string, password: string, isSignup = false) {
try {
const userCredential = isSignup
? await createUserWithEmailAndPassword(auth, email, password)
: await signInWithEmailAndPassword(auth, email, password);
const user = userCredential.user;
// Return important user credentials
return {
uid: user.uid,
email: user.email,
accessToken: await user.getIdToken(),
provider: user.providerId,
createdAt: user.metadata.creationTime,
lastLogin: user.metadata.lastSignInTime,
};
} catch (error: any) {
console.error("Auth Error:", error.code, error.message);
throw error;
}
}Supabase auth
// supabaseAuth.ts
import { supabase } from './supabaseClient';
export async function loginOrSignupSupabase(email: string, password: string, isSignup = false) {
const { data, error } = isSignup
? await supabase.auth.signUp({ email, password })
: await supabase.auth.signInWithPassword({ email, password });
if (error) throw error;
const user = data.user;
const session = data.session;
return {
uid: user?.id,
email: user?.email,
accessToken: session?.access_token,
refreshToken: session?.refresh_token,
createdAt: user?.created_at,
};
}
Clerk auth
// clerkAuth.ts
import clerk from './clerkClient';
export async function loginOrSignupClerk(email: string, password: string, isSignup = false) {
try {
const user = isSignup
? await clerk.users.createUser({ emailAddress: [email], password })
: await clerk.clients.verifyPassword({ identifier: email, password });
const session = await clerk.sessions.createSession({
userId: isSignup ? user.id : user.userId,
});
return {
uid: session.userId,
email,
sessionId: session.id,
createdAt: session.createdAt,
lastActiveAt: session.lastActiveAt,
};
} catch (error: any) {
console.error('Clerk Auth Error:', error);
throw error;
}
}
Quite an easy method to create a user account using email and password.
Moving ahead with new things to pay attention to, what about Google or Github authentication or even LinkedIn
Well, first and foremost step for each to enable the social login credentials needs an OAuth token or API key
Google Auth API Key or Token: Access from the Google console account
Github Auth API Key or Token: Access it from Github OAuth credentials or API token by creating a Github account OAuth application
Twitter Auth API Key or Token: Access it from the X developer account, API key
Read more about each of them, as it's the most important and required method to enable social media login authentication using third-party or serverless.
Session Management
Some of the applications work on a session management concept, where each user, when authenticated within the application, has a session or a time range to access the application. The user is automatically logged out or can't access the application once the session is over.
A typical session management is handled mostly on the server side, but serverless applications do provide ways to manage sessions.
Firebase session management
import { auth } from './firebase';
const currentUser = auth.currentUser;
if (currentUser) {
console.log('Logged in as:', currentUser.email);
}
Access token:
const token = await auth.currentUser?.getIdToken();Firebase auth change
import { onAuthStateChanged } from 'firebase/auth';
onAuthStateChanged(auth, (user) => {
if (user) {
console.log('User is signed in:', user.uid);
} else {
console.log('User is signed out');
}
});Supabase session management
✅ Get Current Session/User
const {
data: { session },
} = await supabase.auth.getSession();
const {
data: { user },
} = await supabase.auth.getUser();
✅ Access Token
const accessToken = session?.access_token;Auto Session Handling
Supabase automatically persists session in localStorage and refreshes tokens.
To listen for auth changes:
supabase.auth.onAuthStateChange((event, session) => {
console.log('Auth event:', event);
console.log('New session:', session);
});✅ Logout
await supabase.auth.signOut();Clerk session management
With Next.js Frontend SDK (@clerk/nextjs)
import { useUser, useAuth, useSession } from "@clerk/nextjs";
const { user } = useUser();
const { getToken } = useAuth();
const { session } = useSession();
const token = await getToken();
Logout (Frontend)
import { useClerk } from "@clerk/nextjs";
const { signOut } = useClerk();
signOut();Backend Session (API route)
import { getAuth } from '@clerk/nextjs/server';
export default async function handler(req, res) {
const { userId, sessionId, getToken } = getAuth(req);
const token = await getToken();
if (!userId) return res.status(401).json({ message: "Unauthorized" });
res.status(200).json({ userId, sessionId, token });
}
Usually, we need session management only for a few categories of applications; other applications that mainly require session management can also be executed using browser storage as local storage or cookies.
Mainly, the categories of application, including heavy payments, large secured files, and user important credentials, need server-side session management; otherwise, cookies might work well.
User authenticated tokens are stored in cookies, which are then used as the token in the API headers to each of the serverless databases for verifying the user.
Firebase cookies management
Delete cookies
import Cookies from "js-cookie";
import { getAuth, signOut } from "firebase/auth";
export async function firebaseLogout() {
await signOut(getAuth());
Cookies.remove("fbToken");
}
Firebase verify cookies
import { NextApiRequest, NextApiResponse } from "next";
import { cert, getApps, initializeApp } from "firebase-admin/app";
import { getAuth as getAdminAuth } from "firebase-admin/auth";
import { parse } from "cookie";
if (!getApps().length) {
initializeApp({ credential: cert(JSON.parse(process.env.FB_ADMIN_KEY!)) });
}
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { fbToken } = parse(req.headers.cookie || "");
if (!fbToken) return res.status(401).json({ message: "Missing token" });
try {
const decoded = await getAdminAuth().verifyIdToken(fbToken);
return res.status(200).json({ uid: decoded.uid, email: decoded.email });
} catch {
return res.status(401).json({ message: "Invalid / expired token" });
}
}Supabase delete cookies
import Cookies from "js-cookie";
import { createClient } from "@supabase/supabase-js";
const supabase = createClient("https://…", "public-anon-key");
export async function supabaseLogout() {
await supabase.auth.signOut();
Cookies.remove("sbToken");
}Supabase verify cookies
This is inside pages/api/verify-cookie.js in next <15 version and for next> 15 version prefer using pages/api/verify-cookie/route.ts
// pages/api/protected/supabase.ts
import { NextApiRequest, NextApiResponse } from "next";
import { createClient } from "@supabase/supabase-js";
import { parse } from "cookie";
const admin = createClient(
"https://…",
process.env.SUPABASE_SERVICE_ROLE_KEY!, // **service-role key only on server**
{ auth: { persistSession: false } }
);
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { sbToken } = parse(req.headers.cookie || "");
if (!sbToken) return res.status(401).json({ message: "Missing token" });
const { data, error } = await admin.auth.getUser(sbToken);
if (error || !data.user)
return res.status(401).json({ message: "Invalid / expired token" });
res.status(200).json({ uid: data.user.id, email: data.user.email });
}
Clerk verify cookies
import { NextApiRequest, NextApiResponse } from "next";
import { Clerk } from "@clerk/clerk-sdk-node";
import { parse } from "cookie";
const clerk = new Clerk({ secretKey: process.env.CLERK_SECRET_KEY! });
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { ckToken } = parse(req.headers.cookie || "");
if (!ckToken) return res.status(401).json({ message: "Missing token" });
try {
const payload = await clerk.verifyToken(ckToken);
res.status(200).json({ uid: payload.sub, email: payload.email_address });
} catch {
res.status(401).json({ message: "Invalid / expired token" });
}
}Creating New Tokens
Most of the applications have shorter user session management, for example, 15 minutes of access once the user is logged in successfully. In that case, we usually prefer the way to recreate the user access token every time the user is asked to authenticate.
For that, we need a method to create new access tokens as shown below
Firebase create a new token
import { getAuth, onAuthStateChanged } from "firebase/auth";
const auth = getAuth();
onAuthStateChanged(auth, async (user) => {
if (user) {
// User is signed in, get their ID token
try {
const idToken = await user.getIdToken();
console.log("Firebase ID Token:", idToken);
// You can now send this token to your backend for verification
} catch (error) {
console.error("Error getting ID token:", error);
}
} else {
// User is signed out
console.log("No user signed in.");
}
});Supabase auth state changed to access the new token
supabase.auth.onAuthStateChanged((event, session) => {
if (event === "SIGNED_IN" || event === "TOKEN_REFRESHED") {
const user = session.user;
const idToken = session.access_token; // or session.refresh_token, depending on your setup
// Use user and idToken as needed
console.log("User:", user);
console.log("ID Token:", idToken);
}
});Clerk auth state changed to create a new token
// /api/clerk/refresh.ts
import { NextApiRequest, NextApiResponse } from "next";
import { clerkClient } from "@clerk/nextjs/server";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const sessionToken = req.cookies["__session"]; // Clerk httpOnly cookie
if (!sessionToken) return res.status(401).json({ error: "No session token" });
try {
const session = await clerkClient.sessions.verifySession(sessionToken);
const newToken = await clerkClient.sessions.createToken(session.id, {
template: "your-jwt-template", // Optional, or omit for default
});
return res.status(200).json({ accessToken: newToken });
} catch {
return res.status(401).json({ error: "Invalid session" });
}
}
It completely depends on the requirements of the project to use any of the above methods in your application.
The usual flow for each application remains the same as follows -
-
User logged in using a third-party or email, password
-
The user provides access to use their credentials, such as email and password
-
The user is then provided with an access token that is stored in cookies
-
User access to the website is provided or revoked depending on the access token verification
Adding 3 YouTube videos for better reference,
That's it, see you in the next one
iHateReading
Summary
This document details Next.js authentication using Firebase, Supabase, Appwrite,and Clerk. Each employs third-party authentication via admin dashboards,requiring initialization using npm modules like firebase/app and @supabase/supabase-js. Email/password signup/login usescreateUserWithEmailAndPassword (Firebase), supabase.auth.signUp (Supabase), and Clerk's SDK. Session management involves accesstokens, onAuthStateChanged listeners (Firebase), and supabase.auth.onAuthStateChange (Supabase). Clerk utilizes its Next.js SDK for session handling and token creation. Cookie management andtoken refreshing are also discussed. Successful authentication relies on token verification.Thisdocument details Next.js authentication using Firebase, Supabase, Appwrite,and Clerk. Each employs third-party authentication via admin dashboards,requiring initialization using npm modules like firebase/app and @supabase/supabase-js. Email/password signup/login usescreateUserWithEmailAndPassword (Firebase), supabase.auth.signUp (Supabase), and Clerk's SDK. Session management involves accesstokens, onAuthStateChanged listeners (Firebase), and supabase.auth.onAuthStateChange (Supabase). Clerk utilizes its Next.js SDK for session handling and token creation. Cookie management andtoken refreshing are also discussed. Successful authentication relies on token verification.
