InAI Auth SDK for Next.js
This skill guides you through integrating InAI Auth into Next.js 14+ applications using the @inai-dev/nextjs package.
Key Facts
- API URL: Always
https://apiauth.inai.dev— hardcoded in the SDK, never configurable - Required env var:
INAI_PUBLISHABLE_KEY=pk_live_... - Package:
@inai-dev/nextjs(depends on@inai-dev/reactand@inai-dev/backend) - Auth modes:
"app"(end users) and"platform"(admin/developer panels)
Installation
npm install @inai-dev/nextjs
Integration Consists of 4 Pieces
- Provider —
<InAIAuthProvider>in root layout - API Route —
app/api/auth/[...inai]/route.ts - Middleware —
middleware.tsat project root - Server helpers —
auth(),currentUser()in server components/actions
1. Provider Setup
// app/layout.tsx
import { InAIAuthProvider } from "@inai-dev/nextjs";
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
<InAIAuthProvider>
{children}
</InAIAuthProvider>
</body>
</html>
);
}
The provider reads the auth_session cookie on mount to hydrate client-side state.
2. API Routes
App User Auth (standard)
// app/api/auth/[...inai]/route.ts
import { createAuthRoutes } from "@inai-dev/nextjs/server";
const { GET, POST } = createAuthRoutes();
export { GET, POST };
Creates these endpoints automatically:
POST /api/auth/login— Login, sets httpOnly cookiesPOST /api/auth/register— RegistrationPOST /api/auth/mfa-challenge— TOTP MFA verificationPOST /api/auth/refresh— Token rotationPOST /api/auth/logout— Invalidate session, clear cookies
Platform Auth (admin panels)
// app/api/auth/[...inai]/route.ts
import { createPlatformAuthRoutes } from "@inai-dev/nextjs/server";
const { GET, POST } = createPlatformAuthRoutes();
export { GET, POST };
Uses /api/platform/auth/* endpoints. No publishable key needed for platform auth.
3. Middleware
// middleware.ts
import { inaiAuthMiddleware } from "@inai-dev/nextjs/middleware";
export default inaiAuthMiddleware({
publicRoutes: ["/", "/about", "/pricing", "/login", "/register"],
signInUrl: "/login",
// jwksUrl: "https://apiauth.inai.dev/.well-known/jwks.json", // optional override
});
export const config = {
matcher: ["/((?!_next/static|_next/image|favicon.ico).*)"],
};
Middleware behavior
- Built-in public routes (
/_next/*,/favicon.ico,/api/*,signInUrl) pass through - Your
publicRoutespass through - Other requests verify
auth_tokenJWT signature using ES256 via JWKS (cached 5 min, auto-retry on key rotation) - Expired token + valid
refresh_token→ auto-refresh via/api/auth/refresh - No valid auth → redirect to
signInUrlwithreturnToparam
publicRoutes as function
publicRoutes: (req) => req.nextUrl.pathname.startsWith("/public/")
Hooks: beforeAuth / afterAuth
export default inaiAuthMiddleware({
publicRoutes: ["/"],
signInUrl: "/login",
beforeAuth: (req) => {
// Return NextResponse to short-circuit
},
afterAuth: (auth, req) => {
// Role-based routing
if (req.nextUrl.pathname.startsWith("/admin") && !auth.has({ role: "admin" })) {
return NextResponse.redirect(new URL("/unauthorized", req.url));
}
},
});
createRouteMatcher
import { inaiAuthMiddleware, createRouteMatcher } from "@inai-dev/nextjs/middleware";
const isAdminRoute = createRouteMatcher(["/admin(.*)"]);
export default inaiAuthMiddleware({
publicRoutes: ["/", "/login"],
afterAuth: (auth, req) => {
if (isAdminRoute(req) && !auth.has({ role: "admin" })) {
return NextResponse.redirect(new URL("/", req.url));
}
},
});
Composing with other middleware (withInAIAuth)
import { withInAIAuth } from "@inai-dev/nextjs/middleware";
import createIntlMiddleware from "next-intl/middleware";
const intlMiddleware = createIntlMiddleware({ locales: ["en", "es"], defaultLocale: "en" });
export default withInAIAuth(intlMiddleware, {
publicRoutes: ["/", "/login"],
signInUrl: "/login",
});
4. Server Helpers
auth()
// Server Component
import { auth } from "@inai-dev/nextjs/server";
export default async function Page() {
const { userId, has, protect, redirectToSignIn } = await auth();
// Option 1: Manual check
if (!userId) redirectToSignIn({ returnTo: "/dashboard" });
// Option 2: protect() — redirects if not authenticated/authorized
const { userId: uid } = protect({ role: "admin" });
// Option 3: Conditional rendering
const canEdit = has({ permission: "content:write" });
return <div>{canEdit && <EditButton />}</div>;
}
currentUser()
import { currentUser } from "@inai-dev/nextjs/server";
// From session cookie (fast, no network)
const user = await currentUser();
// Fresh from API
const freshUser = await currentUser({ fresh: true });
configureAuth() (optional)
import { configureAuth } from "@inai-dev/nextjs/server";
configureAuth({
signInUrl: "/sign-in",
signUpUrl: "/sign-up",
afterSignInUrl: "/dashboard",
afterSignOutUrl: "/sign-in",
publishableKey: process.env.INAI_PUBLISHABLE_KEY,
});
Client Hooks (from @inai-dev/nextjs)
| Hook | Returns |
|---|---|
useAuth() | isLoaded, isSignedIn, userId, roles, permissions, has(), signOut() |
useUser() | isLoaded, isSignedIn, user (full UserResource) |
useSession() | isLoaded, isSignedIn, userId, tenantId, orgId, orgRole, roles, permissions |
useOrganization() | isLoaded, orgId, orgRole |
Pre-Built Components
| Component | Purpose |
|---|---|
<SignIn> | Complete login form with MFA support |
<UserButton> | Avatar dropdown with user info and sign-out |
<SignedIn> | Render children only when authenticated |
<SignedOut> | Render children only when unauthenticated |
<Protect> | Render children if user has required role/permission |
<PermissionGate> | Role/permission gating |
<OrganizationSwitcher> | Org selector dropdown |
"use client";
import { SignedIn, SignedOut, UserButton } from "@inai-dev/nextjs";
export function Navbar() {
return (
<nav>
<SignedIn>
<UserButton showName afterSignOutUrl="/login" />
</SignedIn>
<SignedOut>
<a href="/login">Sign in</a>
</SignedOut>
</nav>
);
}
Cookie Architecture
| Cookie | Purpose | httpOnly | Path | MaxAge |
|---|---|---|---|---|
auth_token | Access JWT | Yes | / | Token expiry |
refresh_token | Refresh JWT | Yes | / | 7 days |
auth_session | User data (readable by JS) | No | / | Token expiry |
The auth_session cookie is what the InAIAuthProvider reads on mount to hydrate client state without a network request.
Server Actions
"use server";
import { auth } from "@inai-dev/nextjs/server";
export async function updateProfile(formData: FormData) {
const { protect } = await auth();
protect(); // userId guaranteed non-null after this
// ... update logic
}
Auth Object Shape
Both server and client auth objects share this structure:
{
userId: string | null
tenantId: string | null
appId: string | null
envId: string | null
orgId: string | null
orgRole: string | null
sessionId: string | null
roles: string[]
permissions: string[]
getToken(): Promise<string | null>
has(params: { role?: string; permission?: string }): boolean
}
Server-only additions: protect(), redirectToSignIn().
Common Patterns
Login form (client-side)
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ email, password }),
});
const data = await res.json();
if (data.mfa_required) {
// Show MFA input, then submit:
await fetch("/api/auth/mfa-challenge", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ mfa_token: data.mfa_token, code: totpCode }),
});
}
// Redirect to dashboard
Role-based page protection
import { auth } from "@inai-dev/nextjs/server";
export default async function AdminPage() {
const { protect } = await auth();
protect({ role: "admin", redirectTo: "/unauthorized" });
return <AdminDashboard />;
}
Source Code Reference
When you need to check implementation details, the source files are at:
packages/nextjs/src/middleware.ts— Middleware implementationpackages/nextjs/src/server.ts— auth(), currentUser(), configureAuth()packages/nextjs/src/api-routes.ts— createAuthRoutes()packages/nextjs/src/platform-api-routes.ts— createPlatformAuthRoutes()packages/nextjs/src/config.ts— Configuration managementpackages/nextjs/src/cookies.ts— Cookie utilitiespackages/react/src/context.tsx— InAIAuthProviderpackages/react/src/hooks/— All React hookspackages/react/src/components/— Pre-built componentspackages/shared/src/jwks.ts— JWKSClient (JWKS key fetching, caching, error throttling)packages/shared/src/jwt.ts— ES256 verification, JWT decoding