Aura Auth
Integrations

Next.js (Pages Router)

Build your first authentication flow with Aura Auth and Next.js Pages Router

This guide walks you through creating a complete authentication flow using Aura Auth with the Next.js Pages Router, API Routes, and Server-Side Rendering.

Overview

Aura Auth and Next.js (Pages Router) both use Node.js HTTP primitives (IncomingMessage and ServerResponse) under the hood. That means Aura Auth needs a small adapter in Pages API Routes, which is included in the example below.

Before continuing, complete the installation and initial setup:

Then use this guide to integrate Aura Auth with a Next.js application using best practices.


What You'll Build

You will create a small Next.js Pages Router setup with:

  • a shared src/lib/auth.ts server configuration
  • a custom pages/api/auth/[...aura].ts adapter that forwards requests to Aura Auth handlers
  • an optional src/lib/auth-client.ts browser client
  • server and client examples for session lookup, sign-in, and sign-out

Project Structure

api
auth
[...aura].ts
auth.ts
auth-client.ts
.env.local

Environment Setup

Create a .env.local file at the root of your project to store secrets securely.

.env.local
# 32-bytes (256-bit) secret used to sign/encrypt sessions. Use a secure random value.
AURA_AUTH_SECRET="base64-or-hex-32-bytes"
AURA_AUTH_SALT="base64-or-hex-32-bytes"
Never commit your .env.local file to version control. Use a secret manager in production.

Setup Aura Auth

HTTP Handlers

Create an auth.ts file in src/lib/ to configure authentication. This file is the shared source of truth for your server handlers, the client helper, and any server-side session utilities.

lib/auth.ts
import { createAuth } from "@aura-stack/auth"

export const auth = createAuth({
  oauth: ["github"],
  basePath: "/api/auth",
  baseURL: "http://localhost:3000",
})

export const { handlers, api } = auth

basePath must match the route mounted in pages/api/auth/[...aura].ts. baseURL should point to your local development server or deployed application URL.

Client API

To use Aura Auth's client-side features, create an auth-client.ts file to initialize the client. This is the browser-facing helper for sign-in buttons, sign-out actions, and session-aware UI.

lib/auth-client.ts
import { createAuthClient } from "@aura-stack/auth/client"

export const authClient = createAuthClient({
  basePath: "/api/auth",
  baseURL: "http://localhost:3000",
})

The baseURL should point to your server's URL, and basePath MUST match the path where your auth routes are mounted.

When you deploy, keep the same basePath but update baseURL to your production domain or environment-specific public URL.

Mount HTTP Handlers

Create a catch-all route to handle all authentication endpoints dynamically. Next.js Pages Router uses standard Node.js IncomingMessage and ServerResponse objects, so the handler below translates them into a standard Web Request before calling Aura Auth.

pages/api/auth/[...aura].ts
import { handlers } from "@/lib/auth"
import type { NextApiRequest, NextApiResponse } from "next"

const getBaseURL = (request: NextApiRequest) => {
  const protocol = request.headers["x-forwarded-proto"] ?? "http"
  const host = request.headers["x-forwarded-host"] ?? request.headers.host
  return `${protocol}://${host}`
}

const toWebHeaders = (headers: NextApiRequest["headers"]) => {
  const webHeaders = new Headers()
  for (const [key, value] of Object.entries(headers)) {
    if (Array.isArray(value)) {
      webHeaders.set(key, value.join(", "))
      continue
    }
    if (typeof value === "string") {
      webHeaders.set(key, value)
    }
  }
  return webHeaders
}

const setResponseHeaders = (res: NextApiResponse, headers: Headers) => {
  for (const [key, value] of headers.entries()) {
    res.setHeader(key, value)
  }
}

export const handler = async (req: NextApiRequest, res: NextApiResponse) => {
  const method = req.method ?? "GET"
  const handler = handlers[method as keyof typeof handlers]
  if (!handler) {
    return res.status(405).json({ error: `Method ${method} Not Allowed` })
  }
  const url = new URL(req.url!, getBaseURL(req))
  const webRequest = new Request(url, {
    method,
    headers: toWebHeaders(req.headers),
    body: method !== "GET" && method !== "HEAD" && req.body ? JSON.stringify(req.body) : undefined,
  })

  try {
    const response = await handler(webRequest)
    setResponseHeaders(res, response.headers)

    if (response.status >= 300 && response.status < 400) {
      const location = response.headers.get("location")
      if (location) {
        return res.redirect(response.status, location)
      }
    }

    const contentType = response.headers.get("content-type") ?? ""
    if (contentType.includes("application/json")) {
      const data = await response.json()
      return res.status(response.status).json(data)
    }

    const text = await response.text()
    return res.status(response.status).send(text)
  } catch {
    return res.status(500).json({ error: "Internal Server Error" })
  }
}

export default handler

This route should be the only file under /api/auth/*. It can handle all auth methods, including sign-in, sign-out, callback, and session checks.

Usage

Server Side Rendering (SSR)

Use server-side rendering when you want to protect a page before any UI is sent to the browser. This is the safest pattern for dashboard pages, account settings, and other private routes.

Get Session

pages/dashboard.tsx
import { api } from "@/lib/auth"
import type { Session } from "@aura-stack/auth"
import type { GetServerSideProps, InferGetServerSidePropsType } from "next"

export const getServerSideProps: GetServerSideProps<{ session: Session | null }> = async ({ req }) => {
  const session = await api.getSession({
    headers: req.headers as Record<string, string>,
  })

  return {
    props: {
      session: session.authenticated ? session.session : null,
    },
  }
}

export default function DashboardPage({ session }: InferGetServerSidePropsType<typeof getServerSideProps>) {
  if (!session) {
    return <p>You must be signed in to view this page.</p>
  }
  return (
    <div>
      <h1>Welcome, {session.user?.name}!</h1>
      <p>Email: {session.user?.email}</p>
    </div>
  )
}

The server-side session lookup is the right place to redirect unauthenticated users or render a fallback state.

Client Side Rendering (CSR)

Use client-side rendering when you need interactive auth controls inside menus, modals, or components that cannot use getServerSideProps.

Sign In

pages/login.tsx
import { authClient } from "@/lib/auth-client"

export default function LoginPage() {
  const signIn = async () => {
    const response = await authClient.signIn("github", {
      redirect: true,
      redirectTo: "/dashboard",
    })
    if (!response.ok) {
      console.error("Sign-in failed:", response.error)
    }
  }

  return (
    <div>
      <h1>Login</h1>
      <button onClick={signIn}>Sign in with GitHub</button>
    </div>
  )
}

If you do not want an automatic redirect, set redirect: false and handle the returned URL yourself.

Get Session

components/user-profile.tsx
import { useEffect, useState } from "react"
import { authClient } from "@/lib/auth-client"

export const UserProfile = () => {
  const [session, setSession] = useState(null)

  useEffect(() => {
    const fetchSession = async () => {
      const { session } = await authClient.getSession()
      setSession(session)
    }
    fetchSession()
  }, [])

  if (!session) return <p>Loading...</p>

  return (
    <div>
      <h1>Welcome back, {session.user?.name}</h1>
      <p>Email: {session.user?.email}</p>
    </div>
  )
}

This pattern works well for menus, avatars, and client-only widgets that need to show the current user after hydration.

Sign Out

components/sign-out-button.tsx
import { authClient } from "@/lib/auth-client"

export const SignOutButton = () => {
  const handleSignOut = async () => {
    await authClient.signOut({
      redirect: true,
      redirectTo: "/",
    })
  }

  return <button onClick={handleSignOut}>Sign Out</button>
}

Client-side sign-out is best when the logout control lives inside a dropdown, toolbar, or any interactive client component.


Common Pitfalls

  • Keep basePath and the route file aligned. If your route is pages/api/auth/[...aura].ts, the auth configuration should use basePath: "/api/auth".
  • Pass the current request headers on the server. api.getSession() needs the active request headers so Aura Auth can read cookies correctly.
  • Treat auth.ts as shared server code. Put the auth instance in src/lib/auth.ts so both API routes and server-side code can reuse it.
  • Use client components only for interaction. Page protection is stronger when it happens on the server before rendering private UI.

Resources

On this page