Logo FormaUI

FormaUI

All providers

Auth0

Logic-only authentication module for Next.js App Router, built on @auth0/nextjs-auth0 v4. No UI is included — email/password screens, "Forgot password?", and OAuth provider buttons are all rendered by Auth0's hosted Universal Login, per Auth0's own recommended integration pattern for Regular Web Applications.

Dependencies

  • @auth0/nextjs-auth0: official Auth0 SDK for Next.js — provides the Auth0Client, the middleware-mounted auth routes (/auth/login, /auth/logout, /auth/callback, /auth/profile), and the useUser() client hook.
  • server-only: build-time guard that throws if service.ts is ever imported into a Client Component bundle.

Folder structure

auth/auth0/
  types.ts        // shared types: AppUser, AppSession, Role, AuthError, mapToAppUser()
  service.ts       // internal — Auth0Client instance + raw SDK/Management API calls
  actions.ts        // "use server" — public entry point for Server Components/forms/buttons
  client.tsx          // AppAuthProvider + isomorphic login/signup/logout URL builders
  hooks.ts              // useAuth, useRequireAuth, and click-handler login/logout helpers

middleware.ts     // PROJECT ROOT, not inside auth/auth0/ — session refresh + route protection
.env.local        // from env.example — Auth0 domain, client credentials, session tuning

There is no route.ts and no index.ts. Every auth endpoint Auth0 itself needs is mounted automatically by the SDK from middleware.ts; the one operation that isn't (account deletion) is a Server Action, not an HTTP route, since nothing external calls it.

How the files fit together

  • service.ts is internal. It holds the single Auth0Client instance and every raw call to Auth0's Authentication API and Management API — session reads, password-reset emails, verification-email resends, account deletion. Nothing here is meant to be imported from a component; actions.ts and middleware.ts are its only two callers.
  • actions.ts is the public door. It's a "use server" module that thinly wraps service.ts, so Server Components, forms, and buttons only ever need one import path for server-side auth logic.
  • client.tsx holds what a Client Component needs that isn't reactive: the optional AppAuthProvider wrapper, and pure URL builders (getLoginUrl, getSignupUrl, getSocialLoginUrl, getLogoutUrl) safe to import from both server and client code, since they're just string construction with no secrets involved.
  • hooks.ts holds what a Client Component needs that is reactive: useAuth() wraps the SDK's useUser() and normalizes it to the same AppUser shape service.ts uses server-side, plus useRequireAuth() as a client-side fallback guard, and click-handler versions of login/signup/logout for buttons that can't use a plain <a href>.
  • middleware.ts lives at the project root, not inside the module folder — Next.js only recognizes middleware there. It's self-contained: the protected/public route configuration is defined at the top of the same file, since it's the only file that needs it.

What actions.ts covers

Everything a Server Component, form, or button needs to drive auth without ever touching service.ts directly:

  • Session and user readsgetSession(), getCurrentUser(), getAccessToken()
  • GuardsrequireAuth() (redirects to login if absent), requireRole(), hasPermission() (placeholder until this module ships more than one role)
  • Password recoverysendPasswordResetEmail(), which also covers "update password" (Auth0 has no direct "change my password while logged in" endpoint without Management API and re-authentication, so both flows use the same hosted reset-email ticket)
  • Email verificationresendVerificationEmail() (the initial verification email is sent automatically by Auth0 on signup)
  • Account deletiondeleteAccountAction(), a Server Action usable directly as a <form action={...}> target. It resolves the caller's user id from their own session rather than accepting one as an argument, so it can never be pointed at a different account, and it redirects to /auth/logout on success to clear both the local session cookie and the Auth0 IdP session in one navigation.

What it deliberately does not cover: registration, login, and the social-provider consent screens. Those are Universal Login URLs (client.tsx), not server logic — Auth0 renders the actual forms.

Usage examples

Get the current user (Server Component)

import { getCurrentUser } from "@/auth/auth0/actions";

export default async function ProfilePage() {
  const user = await getCurrentUser();
  if (!user) return <p>Not signed in</p>;
  return <p>Hello, {user.name} ({user.email})</p>;
}

Get the current user (Client Component, reactive)

"use client";
import { useAuth } from "@/auth/auth0/hooks";

export function UserBadge() {
  const { user, isLoading } = useAuth();
  if (isLoading) return <p>Loading...</p>;
  if (!user) return <p>Not signed in</p>;
  return <p>Hello, {user.name}</p>;
}

Protect a Server Component

import { requireAuth } from "@/auth/auth0/actions";

export default async function DashboardPage() {
  const session = await requireAuth("/dashboard"); // redirects to login if absent
  return <p>Welcome, {session.user.name}</p>;
}

Sign in with Google

import { getSocialLoginUrl } from "@/auth/auth0/client";

<a href={getSocialLoginUrl("google-oauth2")}>Continue with Google</a>
"use client";
import { loginWithProvider } from "@/auth/auth0/hooks";

<button onClick={() => loginWithProvider("google-oauth2")}>
  Continue with Google
</button>

Sign up / log in / log out links

import { getSignupUrl, getLoginUrl, getLogoutUrl } from "@/auth/auth0/client";

<a href={getSignupUrl()}>Sign up</a>
<a href={getLoginUrl()}>Log in</a>
<a href={getLogoutUrl()}>Log out</a>

Password reset

"use server";
import { sendPasswordResetEmail } from "@/auth/auth0/actions";

export async function requestPasswordReset(formData: FormData) {
  const email = formData.get("email") as string;
  const result = await sendPasswordResetEmail(email);
  if (!result.success) {
    // handle result.error.code
  }
}

Delete account

import { deleteAccountAction } from "@/auth/auth0/actions";

<form action={deleteAccountAction}>
  <button type="submit">Delete my account</button>
</form>

Require a role

import { requireRole } from "@/auth/auth0/actions";

export default async function AdminPage() {
  await requireRole("user"); // extend Role in types.ts as more roles are added
  // ...
}

What you can build with this

This module covers the full account lifecycle for anything that needs gated access without building auth screens by hand: SaaS dashboards, member-only content sites, internal tools, or any app where "sign in, stay in, eventually leave" is the extent of what auth needs to do. Combined with a payments module, it's the login layer for a subscription product — getCurrentUser()/requireAuth() gate the paid area, and the role/permission extension points in types.ts are where a "free vs. paid" or "member vs. admin" distinction would plug in once that's needed. Combined with an email module, sendPasswordResetEmail() and resendVerificationEmail() are already the transactional triggers — no separate email-sending logic to write for those two flows.