The auth flow
Integration, server-side verification, and protecting routes.
Integration
Authentication is handled in the server/auth/ directory. BionicJS templates support both managed providers (Clerk) and self-hosted solutions (Better Auth).
Each provider is a plugin package composed under the auth key in bionicjs.config.ts. Choose one:
| Package | Provider | Mount |
|---|---|---|
@bionicjs/better-auth | Better Auth (self-hosted) | /api/auth/[...auth] |
@bionicjs/clerk | Clerk (managed) | Client wrappers + server verify |
@bionicjs/firebase | Firebase | server/auth/firebase.ts |
@bionicjs/supabase | Supabase | server/auth/supabase.ts |
@bionicjs/workos | WorkOS | SSO / directory sync |
The generated project carries the matching server/auth/ handler, and the framework automatically wires up the necessary middleware to protect API routes and verify JWT tokens before they reach your business logic or the Python RPC boundary.
Server-side verification
The handler in server/auth/ is where tokens are verified. The scaffolded pattern reads credentials from the environment and exposes an auth object on @bionicjs/core/server:
1import { betterAuth } from "better-auth";
2
3const secret = process.env.BETTER_AUTH_SECRET;
4const baseURL = process.env.BETTER_AUTH_URL;
5
6if (!secret) throw new Error("BETTER_AUTH_SECRET is not set");
7if (!baseURL) throw new Error("BETTER_AUTH_URL is not set");
8
9export const auth = betterAuth({
10 secret,
11 baseURL,
12 emailAndPassword: { enabled: true },
13});
Environment variables are the contract - each provider template writes its expected keys into .env.example so a fresh clone can be filled in without guessing names.
Protecting routes
Middleware runs before the route handlers and before the Python RPC boundary. A protected API route never performs work without a verified session:
1import { auth } from "@bionicjs/core/server";
2
3export default defineEventHandler(async (event) => {
4 const session = await auth.getSession(event);
5 if (!session) throw createError({ statusCode: 401 });
6 return session.user;
7});
- The websocket, /api, and server actions all go through the same session verification.
- Unverified requests fail in middleware - they never reach a handler or Python.
- Client-side, the frontend renders based on session state from the same
authboundary.
24 / 25