Skip to content

Next.js Integration

@sentinel-auth/nextjs provides Edge Middleware for JWT validation and server helpers for Server Components and Route Handlers.

npm install @sentinel-auth/js @sentinel-auth/nextjs

AuthZ Middleware

Validates dual tokens (IdP + Sentinel authz) at the edge.

// middleware.ts
import { createSentinelAuthzMiddleware } from '@sentinel-auth/nextjs/authz-middleware'

export default createSentinelAuthzMiddleware({
  sentinelUrl: process.env.SENTINEL_URL!,
  idpJwksUrl: 'https://www.googleapis.com/oauth2/v3/certs',
  publicPaths: ['/login', '/auth/callback'],
})
export const config = { matcher: ['/((?!_next|favicon.ico).*)'] }
Option Type Default Description
sentinelUrl string required Sentinel URL (derives JWKS endpoint)
idpJwksUrl string required IdP JWKS URL for token verification
publicPaths string[] [] Paths that skip auth
loginPath string "/login" Redirect for unauthenticated page requests

What it does: strips spoofed x-sentinel-* headers, verifies IdP token against IdP JWKS, verifies authz token against Sentinel JWKS, checks idp_sub binding, sets x-sentinel-* headers for downstream components. API routes get 401 JSON; page routes redirect.

Proxy Middleware

For Sentinel's redirect-based OAuth flow. Validates a single JWT.

// middleware.ts
import { createSentinelMiddleware } from '@sentinel-auth/nextjs/middleware'

export default createSentinelMiddleware({
  jwksUrl: process.env.SENTINEL_JWKS_URL!,
  publicPaths: ['/login', '/auth/callback'],
})
export const config = { matcher: ['/((?!_next|favicon.ico).*)'] }

Additional options: audience (default "sentinel:access"), allowedWorkspaces (optional workspace ID allowlist). Reads token from Authorization: Bearer header or sentinel_access_token cookie.

Headers set by middleware

Both variants set these on success, readable in Server Components and Route Handlers:

Header Value
x-sentinel-user-id User ID
x-sentinel-email Email
x-sentinel-name Display name
x-sentinel-workspace-id Workspace ID
x-sentinel-workspace-slug Workspace slug
x-sentinel-workspace-role Workspace role

Server helpers

import { getUser, requireUser, getToken, withAuth } from '@sentinel-auth/nextjs/server'

getUser() -- returns SentinelUser | null from middleware headers.

// app/dashboard/page.tsx (Server Component)
import { getUser } from '@sentinel-auth/nextjs/server'

export default async function DashboardPage() {
  const user = await getUser()
  if (!user) return <p>Not authenticated</p>
  return <p>Welcome, {user.name}!</p>
}

requireUser() -- returns SentinelUser or throws.

getToken() -- raw JWT string from Authorization header.

withAuth(handler) -- HOC for Route Handlers.

// app/api/notes/route.ts
import { withAuth } from '@sentinel-auth/nextjs/server'

export const GET = withAuth(async (req, user) => {
  return Response.json({ workspace: user.workspaceId })
})

Client components

The default import re-exports all React components with 'use client':

'use client'
import { AuthzProvider, useAuthz, AuthzGuard, AuthzCallback } from '@sentinel-auth/nextjs'

See React Integration for hook and component details.

Complete example

// middleware.ts
import { createSentinelAuthzMiddleware } from '@sentinel-auth/nextjs/authz-middleware'
export default createSentinelAuthzMiddleware({
  sentinelUrl: process.env.SENTINEL_URL!,
  idpJwksUrl: 'https://www.googleapis.com/oauth2/v3/certs',
  publicPaths: ['/login', '/auth/callback'],
})
export const config = { matcher: ['/((?!_next|favicon.ico).*)'] }
// app/login/page.tsx
'use client'
import { AuthzProvider, useAuthz } from '@sentinel-auth/nextjs'
import { IdpConfigs } from '@sentinel-auth/js'

function LoginButton() {
  const { login } = useAuthz()
  return <button onClick={() => login('google')}>Sign in with Google</button>
}

export default function LoginPage() {
  return (
    <AuthzProvider config={{
      sentinelUrl: process.env.NEXT_PUBLIC_SENTINEL_URL!,
      idps: { google: IdpConfigs.google(process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!) },
    }}>
      <LoginButton />
    </AuthzProvider>
  )
}
// app/auth/callback/page.tsx
'use client'
import { AuthzProvider, AuthzCallback } from '@sentinel-auth/nextjs'
import { useRouter } from 'next/navigation'

export default function CallbackPage() {
  const router = useRouter()
  return (
    <AuthzProvider config={{ sentinelUrl: process.env.NEXT_PUBLIC_SENTINEL_URL! }}>
      <AuthzCallback onSuccess={() => router.push('/dashboard')} />
    </AuthzProvider>
  )
}
// app/dashboard/page.tsx (Server Component)
import { getUser } from '@sentinel-auth/nextjs/server'
export default async function DashboardPage() {
  const user = await getUser()
  return <h1>Welcome, {user?.name}</h1>
}