Next.js Integration

Complete guide to integrating @warpy-auth-sdk/core with Next.js App Router

Next.js Integration

This guide covers integrating @warpy-auth-sdk/core with Next.js App Router. The SDK provides multiple integration methods, from zero-config Proxy to custom Route Handlers.

Integration Methods

The SDK offers three integration approaches for Next.js:

  • Next.js 16 Proxy: Zero-config authentication middleware (recommended)
  • Route Handlers: Custom API routes for fine-grained control
  • Server Actions: Server-side authentication in Server Components

Installation

npm install @warpy-auth-sdk/core
# or
yarn add @warpy-auth-sdk/core
# or
pnpm add @warpy-auth-sdk/core

Project Structure

Typical Next.js App Router project structure with authentication:

my-app/
├── app/
│   ├── layout.tsx          # Root layout with AuthProvider
│   ├── page.tsx           # Public home page
│   ├── login/
│   │   └── page.tsx       # Login page
│   ├── dashboard/
│   │   └── page.tsx      # Protected dashboard
│   └── api/
│       └── auth/
│           ├── session/
│           │   └── route.ts    # Session endpoint (if not using Proxy)
│           └── signin/
│               └── email/
│                   └── route.ts # Email auth endpoint (if needed)
├── proxy.ts                # Next.js 16 Proxy (recommended)
└── .env.local             # Environment variables

Environment Configuration

Create a .env.local file with your authentication credentials:

# Required
AUTH_SECRET=your-secret-key-at-least-32-characters-long

# Google OAuth (example)
GOOGLE_CLIENT_ID=your-google-client-id
GOOGLE_CLIENT_SECRET=your-google-client-secret
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google

# Email provider (optional, for magic links)
RESEND_API_KEY=your-resend-api-key
# or SMTP
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USER=your-email@gmail.com
SMTP_PASS=your-app-password

Security Note

The AUTH_SECRET must be at least 32 characters long and kept secret. Generate a secure random string for production.

Method 1: Next.js 16 Proxy (Recommended)

The simplest way to add authentication is using the Next.js 16 Proxy (formerly Middleware). This provides a Clerk-like ergonomic experience with automatic route handling.

Create the Proxy

proxy.ts

Zero-config authentication proxy

import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
import { authMiddleware } from "@warpy-auth-sdk/core/next";
import { google } from "@warpy-auth-sdk/core";

const handler = authMiddleware(
  {
    secret: process.env.AUTH_SECRET!,
    provider: google({
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
      redirectUri: process.env.GOOGLE_REDIRECT_URI!,
    }),
    callbacks: {
      // Resolve/upsert user in your database
      async user(oauthUser) {
        // In production, save to database
        return {
          id: oauthUser.id || oauthUser.email,
          email: oauthUser.email,
          name: oauthUser.name,
          picture: oauthUser.picture,
        };
      },
      jwt: (token) => token,
      session: (session) => session,
    },
  },
  {
    basePath: "/api/auth",
    successRedirect: "/dashboard",
    errorRedirect: "/login",
  }
);

export function proxy(request: NextRequest) {
  const p = request.nextUrl.pathname;
  if (p.startsWith("/api/auth")) return handler(request);
  return NextResponse.next();
}

export const config = {
  matcher: [
    "/((?!_next|[^?]*\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
    "/(api|trpc)(.*)",
  ],
};

Available Endpoints

The Proxy automatically creates these endpoints:

  • GET /api/auth/session - Get current session
  • POST /api/auth/signout - Sign out user
  • GET /api/auth/signin/google - Start Google OAuth
  • GET /api/auth/callback/google - OAuth callback
  • POST /api/auth/signin/email - Send magic link (Node runtime)

Method 2: Custom Route Handlers

For more control, implement custom Route Handlers:

Session Endpoint

app/api/auth/session/route.ts

Get current session

import { getSession } from "@warpy-auth-sdk/core";
import { NextRequest } from "next/server";

export async function GET(request: NextRequest) {
  try {
    const session = await getSession(request, process.env.AUTH_SECRET!);

    return Response.json({ session });
  } catch (error) {
    return Response.json({ session: null }, { status: 401 });
  }
}

Sign-in Endpoint

app/api/auth/signin/google/route.ts

Start Google OAuth flow

import { authenticate } from "@warpy-auth-sdk/core";
import { google } from "@warpy-auth-sdk/core";
import { NextRequest, NextResponse } from "next/server";

const config = {
  secret: process.env.AUTH_SECRET!,
  provider: google({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    redirectUri: process.env.GOOGLE_REDIRECT_URI!,
  }),
};

export async function GET(request: NextRequest) {
  try {
    const result = await authenticate(config, request);

    if (result.redirectUrl) {
      return NextResponse.redirect(result.redirectUrl);
    }

    return Response.json({ error: "Authentication failed" }, { status: 400 });
  } catch (error) {
    console.error("Auth error:", error);
    return NextResponse.redirect("/login?error=auth_failed");
  }
}

OAuth Callback Endpoint

app/api/auth/callback/google/route.ts

Handle OAuth callback

import { authenticate, createSessionCookie } from "@warpy-auth-sdk/core";
import { google } from "@warpy-auth-sdk/core";
import { NextRequest, NextResponse } from "next/server";

const config = {
  secret: process.env.AUTH_SECRET!,
  provider: google({
    clientId: process.env.GOOGLE_CLIENT_ID!,
    clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    redirectUri: process.env.GOOGLE_REDIRECT_URI!,
  }),
  callbacks: {
    async user(oauthUser) {
      // Save to database in production
      return {
        id: oauthUser.email,
        email: oauthUser.email,
        name: oauthUser.name,
        picture: oauthUser.picture,
      };
    },
  },
};

export async function GET(request: NextRequest) {
  try {
    const result = await authenticate(config, request);

    if (result.session) {
      const cookie = createSessionCookie(result.session, config.secret);
      const response = NextResponse.redirect(
        new URL("/dashboard", request.url)
      );
      response.headers.set("Set-Cookie", cookie);
      return response;
    }

    return NextResponse.redirect("/login?error=auth_failed");
  } catch (error) {
    console.error("Callback error:", error);
    return NextResponse.redirect("/login?error=callback_failed");
  }
}

Sign-out Endpoint

app/api/auth/signout/route.ts

Sign out user

import { signOut, clearSessionCookie } from "@warpy-auth-sdk/core";
import { NextRequest, NextResponse } from "next/server";

export async function POST(request: NextRequest) {
  try {
    await signOut(request, process.env.AUTH_SECRET!);

    const response = Response.json({ success: true });
    response.headers.set("Set-Cookie", clearSessionCookie());

    return response;
  } catch (error) {
    return Response.json(
      { error: "Sign out failed" },
      { status: 500 }
    );
  }
}

Client Integration

Root Layout with AuthProvider

Wrap your app with the AuthProvider to enable session management:

app/layout.tsx

Root layout with authentication

import { AuthProvider } from "@warpy-auth-sdk/core/hooks";
import "./globals.css";

export const metadata = {
  title: "My App",
  description: "App with authentication",
};

export default function RootLayout({
  children,
}: {
  children: React.ReactNode;
}) {
  return (
    <html lang="en">
      <body>
        <AuthProvider>
          {children}
        </AuthProvider>
      </body>
    </html>
  );
}

Login Page

app/login/page.tsx

Login page with OAuth

'use client';

import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function LoginPage() {
  const { session, loading } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (session) {
      router.push("/dashboard");
    }
  }, [session, router]);

  if (loading) {
    return <div>Loading...</div>;
  }

  return (
    <div className="min-h-screen flex items-center justify-center">
      <div className="max-w-md w-full space-y-8">
        <h2 className="text-center text-3xl font-bold">
          Sign in to your account
        </h2>

        <a
          href="/api/auth/signin/google"
          className="w-full flex justify-center py-3 px-4 border border-transparent text-sm font-medium rounded-md text-white bg-blue-600 hover:bg-blue-700"
        >
          Continue with Google
        </a>
      </div>
    </div>
  );
}

Protected Dashboard

app/dashboard/page.tsx

Protected page requiring authentication

'use client';

import { useAuth } from "@warpy-auth-sdk/core/hooks";
import { useRouter } from "next/navigation";
import { useEffect } from "react";

export default function DashboardPage() {
  const { session, loading, signOut } = useAuth();
  const router = useRouter();

  useEffect(() => {
    if (!loading && !session) {
      router.push("/login");
    }
  }, [session, loading, router]);

  if (loading) {
    return <div>Loading...</div>;
  }

  if (!session) {
    return null;
  }

  return (
    <div className="min-h-screen bg-gray-50">
      <div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
        <div className="py-16">
          <h1 className="text-3xl font-bold">
            Welcome, {session.user.name}!
          </h1>
          <p className="mt-2 text-gray-600">
            Email: {session.user.email}
          </p>
          <button
            onClick={signOut}
            className="mt-4 bg-red-600 text-white px-4 py-2 rounded-md hover:bg-red-700"
          >
            Sign Out
          </button>
        </div>
      </div>
    </div>
  );
}

Server-Side Session Access

Access the session in Server Components using getServerSession:

app/profile/page.tsx

Server component with session access

import { getServerSession } from "@warpy-auth-sdk/core/hooks/server";
import { redirect } from "next/navigation";

export default async function ProfilePage() {
  const session = await getServerSession();

  if (!session) {
    redirect("/login");
  }

  return (
    <div>
      <h1>Profile</h1>
      <p>Name: {session.user.name}</p>
      <p>Email: {session.user.email}</p>
    </div>
  );
}

Multiple Providers

Support multiple authentication providers:

import { authMiddleware } from "@warpy-auth-sdk/core/next";
import { google, github, email } from "@warpy-auth-sdk/core";

// Note: The SDK currently supports one provider per config
// For multiple providers, you'll need to handle routing logic

const handler = authMiddleware({
  secret: process.env.AUTH_SECRET!,
  provider: google({ /* config */ }),
  callbacks: {
    async user(u) { /* ... */ },
  },
});

Multiple Providers

The SDK currently focuses on single-provider configurations. For multi-provider support, you can implement custom routing logic or use multiple config objects.

TypeScript Support

The SDK includes full TypeScript support with type definitions:

import type {
  AuthConfig,
  Session,
  User
} from "@warpy-auth-sdk/core";

// Session is fully typed
const { session } = useAuth();
// session.user.email - autocomplete works!

Troubleshooting

Session Not Persisting

Ensure cookies are enabled and the AUTH_SECRET is consistent:

// Check if session cookie is being set
const response = NextResponse.redirect("/dashboard");
response.headers.set("Set-Cookie", cookie);

CORS Issues

If your frontend is on a different domain, configure CORS:

export async function GET(request: NextRequest) {
  const response = await fetch(/* ... */);
  response.headers.set("Access-Control-Allow-Origin", "https://yourdomain.com");
  response.headers.set("Access-Control-Allow-Credentials", "true");
  return response;
}

OAuth Redirect Mismatch

Ensure the redirect URI in your OAuth provider settings matches exactly:

# Must match in both .env.local and OAuth provider console
GOOGLE_REDIRECT_URI=http://localhost:3000/api/auth/callback/google

Best Practices

  • Use Environment Variables: Never hardcode secrets
  • Implement CSRF Protection: The SDK handles this automatically for OAuth
  • Validate Sessions Server-Side: Don't trust client-side session data
  • Use HTTPS in Production: Required for secure cookies
  • Set Proper Cookie Attributes: httpOnly, secure, sameSite

Next Steps

Next.js Integration | @warpy-auth-sdk/core