Skip to Content

Next.js SDK (@auris/nextjs)

@auris/nextjs v0.1.0

@auris/nextjs is the recommended package for Next.js applications. It extends @auris/react with server-side utilities that work in Server Components, Route Handlers, and Vercel Edge Middleware.

The package exposes three entry points:

Entry pointEnvironmentContents
@auris/nextjsClient ComponentsRe-exports all of @auris/react
@auris/nextjs/serverServer Components, Route HandlersgetSession, withAuth, requirePermission, checkPermission, createServerManagementClient, createServerFgaClient, getEntitlements, hasEntitlement
@auris/nextjs/middlewareEdge MiddlewareaurisMiddleware

Installation

npm install @auris/nextjs @auris/react @auris/js

Environment Variables

Set these in your .env.local file. Variables prefixed with NEXT_PUBLIC_ are available in Client Components.

# Client-side (both browser and server) NEXT_PUBLIC_AURIS_DOMAIN=https://auth.yourdomain.com NEXT_PUBLIC_AURIS_CLIENT_ID=app_xxxxx NEXT_PUBLIC_APP_URL=http://localhost:3000 # Server-side: used by aurisMiddleware, getSession, withAuth, requirePermission AURIS_DOMAIN=https://auth.yourdomain.com AURIS_CLIENT_ID=app_xxxxx # Server-side only (never exposed to the browser) AURIS_CLIENT_SECRET=cs_live_xxxxx AURIS_TENANT=my-tenant # Optional: enables local JWT verification without a network call AURIS_JWKS_URL=https://auth.yourdomain.com/.well-known/jwks.json

Never include AURIS_CLIENT_SECRET in a variable prefixed with NEXT_PUBLIC_. The secret must remain server-side only.


Setup

Install the package and set environment variables

Follow the installation and environment variable steps above.

Add AurisProvider to your root layout

Wrap your application in a Client Component that provides Auris context to all child Client Components.

// app/providers.tsx 'use client' import { AurisProvider } from '@auris/nextjs' export function Providers({ children }: { children: React.ReactNode }) { return ( <AurisProvider domain={process.env.NEXT_PUBLIC_AURIS_DOMAIN!} clientId={process.env.NEXT_PUBLIC_AURIS_CLIENT_ID!} redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`} > {children} </AurisProvider> ) }
// app/layout.tsx import { Providers } from './providers' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ) }

Configure Edge Middleware

Protect routes before they reach your application logic.

// middleware.ts (at the project root, same level as app/) import { aurisMiddleware } from '@auris/nextjs/middleware' export default aurisMiddleware({ domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, protectedPaths: ['/dashboard/*', '/settings/*'], publicPaths: ['/', '/about', '/pricing', '/auth/*'], loginUrl: '/auth/login', }) export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico|api/public).*)'], }

Create the login and callback pages

// app/auth/login/page.tsx 'use client' import { useAuris } from '@auris/nextjs' import { useEffect } from 'react' export default function LoginPage() { const { loginWithRedirect } = useAuris() useEffect(() => { loginWithRedirect() }, []) return <p>Redirecting to sign in...</p> }
// app/auth/callback/page.tsx 'use client' import { useAuris } from '@auris/nextjs' import { useRouter } from 'next/navigation' import { useEffect } from 'react' export default function CallbackPage() { const { isAuthenticated, isLoading, error } = useAuris() const router = useRouter() useEffect(() => { // AurisProvider completes the code exchange automatically on mount if (!isLoading && isAuthenticated) router.push('/dashboard') }, [isLoading, isAuthenticated, router]) if (error) return <p>Sign in failed: {error.message}</p> return <p>Completing sign in...</p> }

AurisProvider detects the code and state query parameters on mount and performs the token exchange itself — the callback page only has to wait for isAuthenticated and navigate. If you need to trigger the exchange manually (e.g. outside the provider), use useAuris().client.handleRedirectCallback().


Middleware (@auris/nextjs/middleware)

aurisMiddleware(config)

Protects routes at the Edge before they reach your application. Validates the user’s session cookie, and redirects unauthenticated users to the login URL.

Signature:

import { aurisMiddleware } from '@auris/nextjs/middleware' export default aurisMiddleware(config: AurisMiddlewareConfig)

Configuration:

OptionTypeDefaultDescription
domainstringrequiredAuris API base URL
clientIdstringrequiredOAuth client ID
tenantstring'default'Tenant/realm name
protectedPathsstring[][]Paths that require authentication. Exact match, or prefix match with a trailing /* (e.g. '/dashboard/*')
publicPathsstring[][]Paths that bypass auth checks entirely (same matching rules)
loginUrlstring'/login'Where to redirect unauthenticated users. The originally requested path is passed as ?callbackUrl=
callbackPathsstring[]all protected pathsPaths allowed to receive the OAuth2 callback (?code=&state=) without authentication

Path patterns are not regular expressions: a pattern matches either exactly, or as a prefix when it ends in /* ('/dashboard/*' matches /dashboard and everything under it).

// middleware.ts import { aurisMiddleware } from '@auris/nextjs/middleware' export default aurisMiddleware({ domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, protectedPaths: [ '/dashboard/*', '/settings/*', '/api/protected/*', ], publicPaths: [ '/', '/about', '/pricing', '/blog/*', '/auth/*', '/api/public/*', ], loginUrl: '/auth/login', // Only allow the OAuth callback (?code=&state=) on this path callbackPaths: ['/auth/callback'], }) export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], }

The module also exports sanitizeCallbackUrl(url), which restricts a callbackUrl value to a same-origin relative path. Use it in your login page before redirecting to the callbackUrl query parameter written by the middleware:

import { sanitizeCallbackUrl } from '@auris/nextjs/middleware' const target = sanitizeCallbackUrl(searchParams.get('callbackUrl') ?? '/') router.replace(target)

Server Helpers (@auris/nextjs/server)

getSession(config)

Returns the user’s session in a Server Component or Route Handler. Reads the accessToken cookie and validates it against the Auris API (or locally, without a network call, when jwksUrl is set in the config or via the AURIS_JWKS_URL environment variable).

Signature:

import { getSession } from '@auris/nextjs/server' const session = await getSession({ domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, }) // Promise<AurisSession | null>

Returns:

interface AurisSession { userId: string email: string username?: string firstName?: string lastName?: string roles: string[] tenant?: string accessToken: string }
// app/dashboard/page.tsx — Server Component import { getSession } from '@auris/nextjs/server' import { redirect } from 'next/navigation' export default async function DashboardPage() { const session = await getSession({ domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, }) if (!session) redirect('/auth/login') return ( <div> <h1>Welcome, {session.firstName}</h1> <p>Your email: {session.email}</p> <pre>{JSON.stringify(session.roles, null, 2)}</pre> </div> ) }

withAuth(handler, config)

Higher-order function that wraps a Route Handler and injects the authenticated session as req.session. Returns a 401 response if the user is not authenticated, and a 403 response if the optional permission in the config is not granted.

Signature:

import { withAuth } from '@auris/nextjs/server' export const GET = withAuth( async (req: AuthenticatedRequest) => Response, // req.session: AurisSession { domain: string, // required clientId: string, // required tenant?: string, jwksUrl?: string, permission?: string | string[], // optional permission check (403 on failure) } )
// app/api/profile/route.ts import { withAuth } from '@auris/nextjs/server' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export const GET = withAuth(async (req) => { return Response.json({ id: req.session.userId, email: req.session.email, roles: req.session.roles, }) }, aurisConfig) export const PATCH = withAuth(async (req) => { const body = await req.json() const updated = await updateProfile(req.session.userId, body) return Response.json(updated) }, aurisConfig)

requirePermission(permission, config, handler)

Like withAuth, but also enforces a specific permission. Returns a 403 response if the user is authenticated but lacks the required permission.

Signature:

import { requirePermission } from '@auris/nextjs/server' export const DELETE = requirePermission( 'delete:documents', // string | string[] { domain: string, clientId: string, tenant?: string }, async (req: AuthenticatedRequest) => Response // req.session: AurisSession )
// app/api/documents/[id]/route.ts import { requirePermission, withAuth } from '@auris/nextjs/server' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export const GET = withAuth(async (req) => { const id = new URL(req.url).pathname.split('/').at(-1) const doc = await getDocument(id, req.session.userId) return Response.json(doc) }, aurisConfig) export const DELETE = requirePermission( 'delete:documents', aurisConfig, async (req) => { const id = new URL(req.url).pathname.split('/').at(-1) await deleteDocument(id, req.session.userId) return Response.json({ deleted: true }) } )

checkPermission(permission, config)

Returns a boolean indicating whether the current user has a specific permission (or all of the given permissions when passed an array). Designed for Server Components where you need conditional rendering based on permissions.

Signature:

import { checkPermission } from '@auris/nextjs/server' const allowed = await checkPermission('manage:users', { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, }) // Promise<boolean>
// app/admin/page.tsx — Server Component import { getSession } from '@auris/nextjs/server' import { checkPermission } from '@auris/nextjs/server' import { redirect } from 'next/navigation' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export default async function AdminPage() { const session = await getSession(aurisConfig) if (!session) redirect('/auth/login') const canManageUsers = await checkPermission('manage:users', aurisConfig) const canViewReports = await checkPermission('view:reports', aurisConfig) return ( <div> <h1>Admin Panel</h1> {canManageUsers && <UserManagement />} {canViewReports && <ReportsTable />} {!canManageUsers && !canViewReports && <p>You do not have admin access.</p>} </div> ) }

createServerManagementClient(config?)

Creates a Management API client using AURIS_CLIENT_SECRET from environment variables. No configuration required if the environment variables are set; any field can be overridden via the optional config argument.

Signature:

import { createServerManagementClient } from '@auris/nextjs/server' const mgmt = createServerManagementClient()

The client authenticates with the OAuth2 client_credentials grant using:

  • AURIS_DOMAIN (falls back to NEXT_PUBLIC_AURIS_URL)
  • AURIS_CLIENT_ID (falls back to NEXT_PUBLIC_AURIS_CLIENT_ID)
  • AURIS_CLIENT_SECRET
  • AURIS_TENANT (defaults to 'default')
// app/api/admin/users/route.ts import { createServerManagementClient } from '@auris/nextjs/server' import { requirePermission } from '@auris/nextjs/server' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export const GET = requirePermission('manage:users', aurisConfig, async () => { const mgmt = createServerManagementClient() const { users } = await mgmt.users.list({ page: 1, limit: 50 }) return Response.json({ users }) })

createServerFgaClient()

Creates an FGA client with an M2M access token. Use it in Server Components and Route Handlers to perform Fine-Grained Authorization checks server-side.

Signature:

import { createServerFgaClient } from '@auris/nextjs/server' const fga = await createServerFgaClient()
// app/api/documents/[id]/route.ts import { withAuth } from '@auris/nextjs/server' import { createServerFgaClient } from '@auris/nextjs/server' export const GET = withAuth(async (req) => { const docId = new URL(req.url).pathname.split('/').at(-1) const fga = await createServerFgaClient() const { allowed } = await fga.check({ objectType: 'document', objectId: docId, relation: 'viewer', subjectType: 'user', subjectId: req.session.userId, }) if (!allowed) { return Response.json({ error: 'Access denied' }, { status: 403 }) } const doc = await getDocument(docId) return Response.json(doc) }, { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, })

Client Components

All hooks and components from @auris/react are re-exported from @auris/nextjs. In Client Components, import from @auris/nextjs instead of @auris/react:

// In Next.js Client Components — use @auris/nextjs, not @auris/react 'use client' import { useAuris, useUser, usePermissions, AuthGuard, PermissionGate } from '@auris/nextjs'

See the React SDK page for the full reference of all hooks and components.


Complete Application Example

A minimal but complete Next.js application with authentication:

app/ layout.tsx — Root layout with AurisProvider page.tsx — Public home page providers.tsx — Client component wrapper auth/ login/page.tsx — Initiates login redirect callback/page.tsx — Handles redirect from Auris dashboard/ page.tsx — Protected Server Component settings/page.tsx — Protected with permission check api/ profile/route.ts — Protected API route admin/ users/route.ts — Permission-protected API route middleware.ts — Edge protection
// app/providers.tsx 'use client' import { AurisProvider } from '@auris/nextjs' export function Providers({ children }: { children: React.ReactNode }) { return ( <AurisProvider domain={process.env.NEXT_PUBLIC_AURIS_DOMAIN!} clientId={process.env.NEXT_PUBLIC_AURIS_CLIENT_ID!} redirectUri={`${process.env.NEXT_PUBLIC_APP_URL}/auth/callback`} > {children} </AurisProvider> ) }
// app/layout.tsx import { Providers } from './providers' export default function RootLayout({ children }: { children: React.ReactNode }) { return ( <html lang="en"> <body> <Providers>{children}</Providers> </body> </html> ) }
// middleware.ts import { aurisMiddleware } from '@auris/nextjs/middleware' export default aurisMiddleware({ domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, protectedPaths: ['/dashboard/*'], publicPaths: ['/', '/auth/*'], loginUrl: '/auth/login', }) export const config = { matcher: ['/((?!_next/static|_next/image|favicon.ico).*)'], }
// app/auth/login/page.tsx 'use client' import { useAuris } from '@auris/nextjs' import { useEffect } from 'react' export default function LoginPage() { const { loginWithRedirect } = useAuris() useEffect(() => { loginWithRedirect() }, []) return <p>Redirecting to sign in...</p> }
// app/auth/callback/page.tsx 'use client' import { useAuris } from '@auris/nextjs' import { useRouter } from 'next/navigation' import { useEffect } from 'react' export default function CallbackPage() { const { isAuthenticated, isLoading } = useAuris() const router = useRouter() useEffect(() => { // AurisProvider completes the code exchange automatically on mount if (!isLoading && isAuthenticated) router.push('/dashboard') }, [isLoading, isAuthenticated, router]) return <p>Completing sign in...</p> }
// app/dashboard/page.tsx — Server Component import { getSession } from '@auris/nextjs/server' import { checkPermission } from '@auris/nextjs/server' import { redirect } from 'next/navigation' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export default async function DashboardPage() { const session = await getSession(aurisConfig) if (!session) redirect('/auth/login') const isAdmin = await checkPermission('manage:users', aurisConfig) return ( <main> <h1>Welcome, {session.firstName}</h1> {isAdmin && ( <a href="/dashboard/admin">Go to Admin Panel</a> )} </main> ) }
// app/api/profile/route.ts import { withAuth } from '@auris/nextjs/server' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export const GET = withAuth(async (req) => { return Response.json({ id: req.session.userId, email: req.session.email, firstName: req.session.firstName, roles: req.session.roles, }) }, aurisConfig) export const PATCH = withAuth(async (req) => { const body = await req.json() const updated = await updateProfile(req.session.userId, body) return Response.json(updated) }, aurisConfig)
// app/api/admin/users/route.ts import { requirePermission, createServerManagementClient } from '@auris/nextjs/server' const aurisConfig = { domain: process.env.AURIS_DOMAIN!, clientId: process.env.AURIS_CLIENT_ID!, } export const GET = requirePermission('manage:users', aurisConfig, async () => { const mgmt = createServerManagementClient() const result = await mgmt.users.list({ page: 1, limit: 50 }) return Response.json(result) })