Aura Auth
Integrations

Next.js (App Router)

Integrate Aura Auth and Next.js App Router

This guide walks you through creating a complete authentication flow using Aura Auth directly with the Next.js App Router's native Route Handlers and Server Actions.

Overview

Aura Auth and Next.js App Router both follow the web-standard Request and Response interfaces for high-performance HTTP handling. That makes the integration straightforward: you can mount Aura Auth directly in Route Handlers and use the same api object in Server Components and Server Actions.

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 App Router setup with:

  • a shared src/lib/auth.ts server configuration
  • an app/api/auth/[...aura]/route.ts handler
  • an optional src/lib/auth-client.ts browser client
  • server and client examples for sign-in, session lookup, and sign-out

Project Structure

route.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 and export the helpers used by both route handlers and server 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, jose, api } = auth

basePath must match the route mounted in app/api/auth/[...aura]/route.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.

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.

Mount HTTP Handlers

Create a catch-all route to handle all authentication endpoints dynamically. Next.js App Router uses standard Web Request and Response objects, so the route can forward requests directly to Aura Auth handlers.

app/api/auth/[...aura]/route.ts
import { handlers } from "@/lib/auth"

export const { GET, POST, PATCH } = handlers

This route handles all requests under /api/auth/*.

Usage

Server Side Rendering (SSR)

The api object is designed for server environments such as Server Components and Server Actions.

When you need the active session on the server, pass the current request headers using headers() from next/headers. That gives Aura Auth access to the cookies and headers required to identify the session.

Sign-In

app/login/page.tsx
import { api } from "@/lib/auth"
import { redirect } from "next/navigation"

export default function LoginPage() {
  const signInAction = async () => {
    "use server"
    const signIn = await api.signIn("github", {
      redirect: false,
      redirectTo: "/dashboard",
    })
    redirect(signIn.signInURL)
  }

  return (
    <form action={signInAction}>
      <button type="submit">Sign in with GitHub</button>
    </form>
  )
}

Use this pattern when you want the server action to own the redirect destination while Aura Auth handles the provider flow.

Get Session

app/dashboard/page.tsx
import { api } from "@/lib/auth"
import { headers } from "next/headers"
import { redirect } from "next/navigation"

export default async function DashboardPage() {
  const headersStore = await headers()

  const { session, authenticated } = await api.getSession({ headers: headersStore })
  if (!authenticated) {
    redirect("/login")
  }

  return (
    <div>
      <h1>Welcome back, {session.user?.name}</h1>
      <img src={session.user?.image} alt="Profile Avatar" />
    </div>
  )
}

This is the recommended pattern for protected App Router pages: check the session before rendering any private UI.

Sign Out

app/dashboard/page.tsx
import { api } from "@/lib/auth"
import { headers } from "next/headers"
import { redirect } from "next/navigation"

export default async function DashboardPage() {
  const signOutAction = async () => {
    "use server"
    const headerStore = await headers()
    const response = await api.signOut({
      headers: headerStore,
    })
    if (response.ok) {
      redirect("/home")
    }
  }
  return (
    <form action={signOutAction}>
      <button type="submit">Sign Out</button>
    </form>
  )
}

If you prefer a client-only interaction, you can call authClient.signOut() from a client component instead.

Client Side Rendering (CSR)

Sign In

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

export const SignInButton = () => {
  const handleSignIn = async () => {
    await authClient.signIn("github", {
      redirectTo: "/dashboard",
    })
  }

  return <button onClick={handleSignIn}>Sign in with GitHub</button>
}

Use a client component when you need a clickable auth button, custom modal, or other interactive sign-in entry point.

Get Session

app/components/user-profile.tsx
"use client"
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 <div>Loading...</div>

  return (
    <div>
      <h1>Welcome back, {session.user?.name}</h1>
      <img src={session.user?.image} alt="Profile Avatar" />
    </div>
  )
}

This is useful for session-aware UI that updates after hydration or after a client-side navigation.

Sign Out

To easily check sessions or log out from client components, use createAuthClient.

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

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

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

Client-side sign-out is a better fit for navigation menus, profile popovers, and other interactive UI.


Common Pitfalls

  • Mismatched Paths: Ensure the basePath in your auth configuration matches the path where your route handlers are mounted. For example, if your handlers are in app/api/auth/[...aura]/route.ts, then basePath should be /api/auth.
  • Incorrect Headers: When calling api.getSession or api.signOut from server components or actions, always pass the current request headers using Next.js's headers() function. This allows Aura Auth to read cookies and manage sessions correctly.
  • Don't update cookies in signOut: When signing out is called, Aura Auth will return a response with Set-Cookie headers and it's required to set the cookies using cookies function.

Resources

On this page