AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. looks simple, a login form, a password check, a sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. cookieWhat is cookie?A small piece of data the browser stores and automatically sends with every request to the matching server, often used for sessions.. But then you add "forgot password", then 2FAWhat is 2fa?Two-factor authentication - requiring a second verification step (like a phone code) on top of your password to prove it's really you., then Google login, then admin impersonation, then suspicious login alerts. Before long you've built a miniature auth service inside your app. Auth providers exist to stop you from doing that.
Why delegate authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token.
Every auth system you build yourself is a liability. Password hashingWhat is hashing?A one-way mathematical transformation that turns data (like a password) into a fixed-length string that can't be reversed. Used to store passwords securely. algorithms change, vulnerabilities are discovered, sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. management is subtle. Auth providers have dedicated security teams watching for these issues so you don't have to.
Beyond security, they give you things that would take weeks to build:
| Feature | Build it yourself | Use Clerk/Auth0 |
|---|---|---|
| Password hashing + salting | Implement bcrypt correctly | Handled |
| "Forgot password" flow | Email token + expiry logic | Handled |
| Two-factor authentication | TOTP library + backup codes | Handled |
| Google/GitHub SSO | OAuth flow per provider | Toggle in dashboard |
| Session management | Cookie + database + expiry | Handled |
| User admin dashboard | Build your own | Included |
The main trade-off is cost and vendor dependencyWhat is dependency?A piece of code written by someone else that your project needs to work. Think of it as a building block you import instead of writing yourself.. Auth providers are usually free up to a certain number of users, then charge per monthly active user. At scale, the cost can be significant.
Setting up Clerk
Installation and configuration
Clerk's Next.js SDKWhat is sdk?A pre-built library from a service provider that wraps their API into convenient functions you call in your code instead of writing raw HTTP requests. wraps your entire app in a providerWhat is provider?A wrapper component that makes data available to all components nested inside it without passing props manually. that makes the current user available everywhere, client components, server components, and APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. routes.
npm install @clerk/nextjsAdd your keys to .env.local:
NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=pk_test_...
CLERK_SECRET_KEY=sk_test_...Protect routes with middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.. This runs on every request and redirects unauthenticated users to the sign-in page:
// middleware.ts (at the root of your project)
import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server';
const isPublicRoute = createRouteMatcher([
'/',
'/sign-in(.*)',
'/sign-up(.*)',
'/api/webhooks(.*)' // Webhooks must be public - they don't have user sessions
]);
export default clerkMiddleware((auth, req) => {
if (!isPublicRoute(req)) {
auth().protect(); // Redirects to sign-in if not authenticated
}
});
export const config = {
matcher: ['/((?!.+\\.[\\w]+$|_next).*)', '/', '/(api|trpc)(.*)']
};Using the current user
In server components and API routes, use currentUser() or auth() to get the authenticated user:
import { currentUser, auth } from '@clerk/nextjs/server';
// In a Server Component
export default async function Dashboard() {
const user = await currentUser();
if (!user) {
return <div>Not logged in</div>;
}
return (
<div>
<h1>Welcome back, {user.firstName}</h1>
<p>Email: {user.emailAddresses[0].emailAddress}</p>
</div>
);
}
// In an API route
export async function GET(req: Request) {
const { userId } = auth();
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
const data = await getUserData(userId);
return Response.json(data);
}In client components, use the useUser hookWhat is hook?A special function in React (starting with "use") that lets you add state, side effects, or other React features to a component without writing a class.:
'use client';
import { useUser, UserButton } from '@clerk/nextjs';
export function Header() {
const { user, isLoaded } = useUser();
if (!isLoaded) return <div>Loading...</div>;
return (
<header>
<span>Hello, {user?.firstName}</span>
<UserButton /> {/* Pre-built avatar + dropdown with sign-out */}
</header>
);
}UserButton component gives you a polished user menu with sign-out, profile management, and account switching out of the box. You can drop it in and move on.Auth0 as an alternative
Auth0 follows a similar model but with more configuration and a different APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. style. It's been around longer and is more common in enterprise environments.
// Auth0 with Next.js
import { withPageAuthRequired, getSession } from '@auth0/nextjs-auth0';
// Protect a page
export default withPageAuthRequired(function Dashboard({ user }) {
return <div>Hello {user.name}</div>;
});
// In API routes
import { getSession } from '@auth0/nextjs-auth0';
export async function GET(req: Request) {
const session = await getSession();
if (!session) {
return Response.json({ error: 'Unauthorized' }, { status: 401 });
}
return Response.json({ userId: session.user.sub });
}Understanding JWTs
Both Clerk and Auth0 use JWTs (JSON Web TokensWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup.) under the hood. A JWT is a signed, encoded string that contains claims about the user, their ID, email, roles. Your server can verify a JWT without hitting a database, because the signature proves it came from the auth providerWhat is provider?A wrapper component that makes data available to all components nested inside it without passing props manually..
eyJhbGciOiJSUzI1NiJ9.eyJ1c2VyX2lkIjoiMTIzIiwiZW1haWwiOiJ1c2VyQGV4YW1wbGUuY29tIn0.SIGNATURE
^^ header ^^ ^^ payload (base64) ^^ ^^ sig ^^import jwt from 'jsonwebtoken';
function verifyClerkToken(token) {
try {
// Clerk provides the public key to verify with
const payload = jwt.verify(token, process.env.CLERK_JWT_PUBLIC_KEY, {
algorithms: ['RS256']
});
return payload;
} catch (err) {
return null; // Token invalid or expired
}
}Quick reference
| Task | Clerk | Auth0 |
|---|---|---|
| Get current user (server) | currentUser() or auth() | getSession() |
| Get current user (client) | useUser() hook | useUser() hook |
| Protect a page | auth().protect() in middleware | withPageAuthRequired() |
| Sign out | <UserButton /> or signOut() | <LogoutButton /> |
| Get user ID | auth().userId | session.user.sub |
// API route protection with Clerk
import { getAuth } from '@clerk/nextjs/server';
export default async function handler(req, res) {
const { userId } = getAuth(req);
if (!userId) {
return res.status(401).json({ error: 'Unauthorized' });
}
// Protected route
const userData = await getUserData(userId);
res.json(userData);
}
// Server-side JWT verification
import jwt from 'jsonwebtoken';
function verifyToken(token) {
try {
return jwt.verify(token, process.env.CLERK_JWT_KEY);
} catch {
return null;
}
}