Back to blog

Auth for non-Next.js stacks


Authentication is not limited to Next.js, and the current ecosystem is broader than that shorthand suggests. Auth.js documents integrations for Next.js, Qwik, SvelteKit, and Express. Better Auth describes itself as framework-agnostic. The useful question for a SolidStart, Astro, Express, Fastify, or Hono application is therefore not whether authentication exists outside Next.js. It is how directly the library fits the framework's request lifecycle and how much framework-specific auth code the application must maintain.

Framework-independent has a concrete meaning

A framework-independent auth engine keeps credential verification, session creation, OAuth transactions, token consumption, rate limits, and auth errors outside the router integration. The framework contributes a route, the incoming request, and the outgoing response. Protected application routes then read the session credential and ask the auth engine for the current user and session.

The cleanest boundary in modern JavaScript is the Web Request and Response API. SolidStart API routes receive a Request. Astro server endpoints receive one in their route context. Hono is built around Web Standards and exposes its raw Request. Express and Fastify use their own request and response objects, so their integrations need a small adapter that preserves the request body, headers, response status, and repeated Set-Cookie headers.

One Own Auth handler owns the auth routes

Own Auth 0.3.6 exposes createOwnAuthHandler, which accepts a standard Web Request and returns a Response. Mounted under /api/auth, it provides the package's sign-up, sign-in, sign-out, session, recovery, MFA, passkey, invitation, and configured OAuth routes. It also owns browser-origin checks, request parsing, auth cookies, and the public error format.

src/lib/auth-handler.ts
import { createOwnAuthHandler } from "own-auth/http";

import { auth } from "./auth";

export const authHandler = createOwnAuthHandler(auth);

This handler is the shared integration surface. The framework files below contain routing code, not reimplementations of authentication. Password policy, session expiry, OAuth account linking, and MFA result handling remain consistent when the surrounding application moves between frameworks.

SolidStart uses a catch-all API route

SolidStart maps files that export HTTP method functions to API routes. A catch-all route keeps the complete Own Auth path below one mount and passes event.request straight to the handler. GET covers session reads and provider callbacks; POST covers authentication mutations and Apple's form-post OAuth callback.

src/routes/api/auth/[...path].ts
import type { APIEvent } from "@solidjs/start/server";

import { authHandler } from "~/lib/auth-handler";

function handle({ request }: APIEvent) {
  return authHandler(request);
}

export const GET = handle;
export const POST = handle;

Protected SolidStart endpoints use the same request object with readSessionToken, then call auth.getCurrentSession. That keeps the session check on the server and leaves Solid components free to consume a small account endpoint or server-loaded user object.

Astro mounts the same handler in SSR mode

Astro endpoints also export functions named for HTTP methods. In server mode, the endpoint runs for each request and can return the handler's Response unchanged. Astro middleware can separately verify the session and attach the result to Astro.locals for protected pages.

src/pages/api/auth/[...path].ts
import type { APIRoute } from "astro";

import { authHandler } from "../../../lib/auth-handler";

export const GET: APIRoute = ({ request }) => authHandler(request);
export const POST = GET;

Hono passes its raw request

Hono already uses Web Request and Response objects. Its integration is one route expression: send c.req.raw to the handler and return the resulting response. Hono middleware can verify sessions for application routes without changing the mounted auth API.

src/server.ts
import { Hono } from "hono";

import { authHandler } from "./lib/auth-handler";

const app = new Hono();

app.all("/api/auth/*", (c) => authHandler(c.req.raw));

export default app;

Express and Fastify need transport adapters

Express routing can match the full auth subtree with app.all, but Express has already wrapped Node's request and response types. The adapter must construct a Web Request before body middleware consumes the stream, then copy status, headers, cookies, and body from the returned Response. Mounting order is part of the integration contract.

Fastify provides route encapsulation and content-type parsers. Its Own Auth route preserves the raw body for the handler and keeps the auth parser isolated from the rest of the application. Both frameworks can carry a trusted client IP into the handler for the OAuth and One Tap request context, using their configured proxy trust rather than arbitrary forwarded headers.

The application still owns the framework layer

Framework independence does not mean identical files. SolidStart and Astro use file routing. Hono uses a Web-standards router. Express and Fastify bridge Node-specific transports. Each application still decides where protected routes live, how account state reaches the UI, and where product authorization runs. The auth engine should make those differences explicit without making them part of credential or session behavior.

That boundary is what makes a non-Next.js integration durable. A framework upgrade changes the mount and request adapter. An authentication upgrade changes the package and its migrations. Keeping those concerns separate lets teams use SolidStart, Astro, Hono, Express, or Fastify without treating their choice of web framework as their choice of identity architecture.