Authsome

Next.js App Guide

Build a complete Next.js application with Authsome UI -- authentication, route protection, and user management.

This guide walks through building a full Next.js application with Authsome authentication from scratch. You will set up the provider, add middleware for route protection, create sign-in and sign-up pages, build a dashboard with user profile management, and add organization switching.

1. Create a Next.js app

npx create-next-app@latest my-authsome-app --typescript --tailwind --app --src-dir
cd my-authsome-app

2. Install Authsome packages

npm install @authsome/ui-nextjs @authsome/ui-react @authsome/ui-core @authsome/ui-components

3. Environment variables

# .env.local
NEXT_PUBLIC_AUTHSOME_URL=http://localhost:8080
AUTHSOME_API_URL=http://localhost:8080
NEXT_PUBLIC_AUTHSOME_KEY=pk_live_xxxxx  # Optional: publishable key for auto-discovery

NEXT_PUBLIC_AUTHSOME_URL is used by client components. AUTHSOME_API_URL is used by server components and middleware (never exposed to the browser). NEXT_PUBLIC_AUTHSOME_KEY enables auto-discovery of which auth methods (social, passkey, MFA, etc.) are enabled on your backend.

4. Set up AuthProvider

Create a client-side providers component that wraps your app with AuthProvider and cookie-based storage:

// src/app/providers.tsx
"use client";

import { AuthProvider, createCookieStorage } from "@authsome/ui-nextjs";
import type { ClientConfig } from "@authsome/ui-nextjs";

export function Providers({
  children,
  initialClientConfig,
}: {
  children: React.ReactNode;
  initialClientConfig?: ClientConfig;
}) {
  return (
    <AuthProvider
      baseURL={process.env.NEXT_PUBLIC_AUTHSOME_URL!}
      storage={createCookieStorage()}
      publishableKey={process.env.NEXT_PUBLIC_AUTHSOME_KEY}
      initialClientConfig={initialClientConfig}
    >
      {children}
    </AuthProvider>
  );
}

Update the root layout to use the providers with server-side autoconfig:

// src/app/layout.tsx
import type { Metadata } from "next";
import { getClientConfig } from "@authsome/ui-nextjs";
import { Providers } from "./providers";
import "./globals.css";

export const metadata: Metadata = {
  title: "My Authsome App",
  description: "Next.js app with Authsome authentication",
};

export default async function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  const clientConfig = await getClientConfig({
    baseURL: process.env.AUTHSOME_API_URL!,
    publishableKey: process.env.NEXT_PUBLIC_AUTHSOME_KEY,
  });

  return (
    <html lang="en">
      <body>
        <Providers initialClientConfig={clientConfig ?? undefined}>
          {children}
        </Providers>
      </body>
    </html>
  );
}

5. Add the proxy route handler

Create a catch-all API route that proxies requests to the Authsome backend. This is required for social login (OAuth callbacks), magic link verification, and cookie-based session management.

// src/app/api/authsome/[...path]/route.ts
import { createProxyHandler } from "@authsome/ui-nextjs/proxy";

const handler = createProxyHandler({
  baseURL: process.env.NEXT_PUBLIC_AUTHSOME_URL!,
});

export { handler as GET, handler as POST, handler as PUT, handler as DELETE, handler as PATCH };

All requests to /api/authsome/* will be forwarded to the corresponding path on your Authsome backend. The handler automatically forwards cookies and authorization headers.

6. Add middleware for route protection

Create the middleware file at the project root. All routes except the ones listed in publicPaths will require authentication. The authPaths option redirects already-authenticated users away from sign-in/sign-up pages.

// src/middleware.ts
import { createAuthMiddleware } from "@authsome/ui-nextjs/middleware";

export default createAuthMiddleware({
  baseURL: process.env.AUTHSOME_API_URL!,
  signInPage: "/sign-in",
  publicPaths: [
    "/",
    "/sign-in",
    "/sign-up",
    "/forgot-password",
    "/reset-password",
    "/api/authsome/*",
  ],
  afterSignInUrl: "/dashboard",
});

export const config = {
  matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};

Important: Include /api/authsome/* in publicPaths so the proxy route handler is accessible without authentication.

7. Create the sign-in page

// src/app/sign-in/page.tsx
"use client";

import { useRouter, useSearchParams } from "next/navigation";
import { SignInForm } from "@authsome/ui-components";

export default function SignInPage() {
  const router = useRouter();
  const searchParams = useSearchParams();
  const redirect = searchParams.get("redirect") || "/dashboard";

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-50">
      <div className="w-full max-w-md">
        <SignInForm
          onSuccess={() => router.push(redirect)}
          showForgotPassword
          forgotPasswordHref="/forgot-password"
          signUpHref="/sign-up"
          socialProviders={[
            { provider: "google", label: "Continue with Google" },
            { provider: "github", label: "Continue with GitHub" },
          ]}
        />
      </div>
    </div>
  );
}

8. Create the sign-up page

// src/app/sign-up/page.tsx
"use client";

import { useRouter } from "next/navigation";
import { SignUpForm } from "@authsome/ui-components";

export default function SignUpPage() {
  const router = useRouter();

  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-50">
      <div className="w-full max-w-md">
        <SignUpForm
          onSuccess={() => router.push("/dashboard")}
          signInHref="/sign-in"
        />
      </div>
    </div>
  );
}

9. Create the dashboard

Server component with session validation

// src/app/dashboard/page.tsx
import { getServerSession } from "@authsome/ui-nextjs";
import { redirect } from "next/navigation";
import { DashboardClient } from "./dashboard-client";

export default async function DashboardPage() {
  const session = await getServerSession({
    baseURL: process.env.AUTHSOME_API_URL!,
  });

  if (!session) {
    redirect("/sign-in");
  }

  return <DashboardClient initialUser={session.user} />;
}

Client component with user profile

// src/app/dashboard/dashboard-client.tsx
"use client";

import { useAuth, useUser } from "@authsome/ui-nextjs";
import {
  UserProfileCard,
  UserButton,
  OrgSwitcher,
  SessionList,
} from "@authsome/ui-components";
import type { User } from "@authsome/ui-core";

export function DashboardClient({ initialUser }: { initialUser: User }) {
  const { signOut } = useAuth();
  const { user } = useUser();

  const displayUser = user || initialUser;

  return (
    <div className="min-h-screen bg-gray-50">
      {/* Header */}
      <header className="border-b bg-white px-6 py-4 flex items-center justify-between">
        <h1 className="text-xl font-semibold">Dashboard</h1>
        <div className="flex items-center gap-4">
          <OrgSwitcher onOrgChange={(orgId) => console.log("Switched:", orgId)} />
          <UserButton />
        </div>
      </header>

      {/* Content */}
      <main className="max-w-4xl mx-auto p-6 space-y-8">
        <section>
          <h2 className="text-lg font-medium mb-4">Profile</h2>
          <UserProfileCard user={displayUser} />
        </section>

        <section>
          <h2 className="text-lg font-medium mb-4">Active Sessions</h2>
          <SessionList />
        </section>

        <button
          onClick={signOut}
          className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600"
        >
          Sign out
        </button>
      </main>
    </div>
  );
}

10. Add organization switching

Create an organization settings page:

// src/app/dashboard/organizations/page.tsx
"use client";

import { useOrganizations } from "@authsome/ui-nextjs";
import { OrgSwitcher } from "@authsome/ui-components";

export default function OrganizationsPage() {
  const { organizations, total, isLoading, reload } = useOrganizations();

  return (
    <div className="max-w-2xl mx-auto p-6">
      <h1 className="text-2xl font-bold mb-6">Organizations</h1>

      <div className="mb-6">
        <OrgSwitcher onOrgChange={(orgId) => console.log("Active:", orgId)} />
      </div>

      <div className="space-y-4">
        {organizations.map((org) => (
          <div key={org.id} className="border rounded-lg p-4">
            <h3 className="font-medium">{org.name}</h3>
            <p className="text-sm text-gray-500">{org.id}</p>
          </div>
        ))}
      </div>

      {total === 0 && !isLoading && (
        <p className="text-gray-500">You are not a member of any organizations.</p>
      )}
    </div>
  );
}

11. Add a forgot-password page

// src/app/forgot-password/page.tsx
"use client";

import { ForgotPasswordForm } from "@authsome/ui-components";

export default function ForgotPasswordPage() {
  return (
    <div className="flex min-h-screen items-center justify-center bg-gray-50">
      <div className="w-full max-w-md">
        <ForgotPasswordForm signInHref="/sign-in" />
      </div>
    </div>
  );
}

12. Project structure

After completing the guide, your project should look like this:

src/
  app/
    layout.tsx              # Root layout with server-side autoconfig
    providers.tsx           # AuthProvider + cookie storage
    page.tsx                # Landing page (public)
    sign-in/page.tsx        # Sign-in page (public)
    sign-up/page.tsx        # Sign-up page (public)
    forgot-password/page.tsx # Forgot password (public)
    api/
      authsome/
        [...path]/route.ts  # Proxy route handler
    dashboard/
      page.tsx              # Dashboard (protected, server component)
      dashboard-client.tsx  # Dashboard (client component)
      organizations/
        page.tsx            # Organization management
  middleware.ts             # Auth middleware

13. Deploy considerations

Vercel

  • Set AUTHSOME_API_URL and NEXT_PUBLIC_AUTHSOME_URL in the Vercel dashboard environment variables
  • The middleware runs on Vercel's Edge Network, so token validation happens close to the user
  • Ensure your Authsome API is reachable from Vercel's Edge functions

Docker

FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .
RUN npm run build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public

ENV AUTHSOME_API_URL=http://authsome:8080
ENV NEXT_PUBLIC_AUTHSOME_URL=https://auth.example.com

EXPOSE 3000
CMD ["node", "server.js"]

Security checklist

  • Set secure: true in createCookieStorage() for production (HTTPS)
  • Use sameSite: "strict" if your auth API is on the same domain
  • Never expose AUTHSOME_API_URL (without NEXT_PUBLIC_ prefix) to the client
  • Consider adding CSP headers to prevent XSS attacks
  • Enable rate limiting and account lockout on your Authsome server

On this page