Back to guides

Own Auth with SolidStart


SolidStart API routes receive a standard Web Request, so they can pass authentication traffic directly to createOwnAuthHandler. The handler owns the auth endpoint contract and session cookies; SolidStart keeps the application pages and protected routes.

Install Own Auth

Terminal
npm install own-auth
npx own-auth migrate
.env
DATABASE_URL=postgres://user:password@localhost:5432/myapp
OWN_AUTH_TOKEN_PEPPER=replace-with-a-long-random-secret

Create the auth instance

src/lib/auth.ts
import { createOwnAuth } from "own-auth";

export const auth = createOwnAuth({
  tokenPepper: process.env.OWN_AUTH_TOKEN_PEPPER,
});

Keep this module in server code. The catch-all route below handles sign-up, sign-in, session lookup, sign-out, recovery, MFA, passkeys, and configured provider callbacks without duplicating those operations as SolidStart actions.

Mount the HTTP handler

SolidStart maps the catch-all filename to every path below /api/auth. Export GET and POST because the Own Auth API uses both methods, including GET callbacks for Google and GitHub and the POST callback used by Apple.

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

import { auth } from "~/lib/auth";

const authHandler = createOwnAuthHandler(auth);

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

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

Call it from the browser

The framework-neutral client calls /api/auth on the current origin. Completed authentication updates its session snapshot from the handler response; an MFA result contains the available methods and expiry instead of a completed user session.

src/lib/auth-client.ts
import { createOwnAuthClient } from "own-auth/client";

export const authClient = createOwnAuthClient();
src/lib/sign-in.ts
import { authClient } from "./auth-client";

export async function signIn(email: string, password: string) {
  const result = await authClient.signInEmailPassword({ email, password });

  if (result.status === "mfa_required") {
    return { next: "/mfa", methods: result.methods };
  }

  const { id, email, name, imageUrl } = result.user;
  return { next: "/account", user: { id, email, name, imageUrl } };
}

Protect an API route

readSessionToken accepts the same Web Request supplied by SolidStart. Verify the opaque token before returning account data or using the user ID in application queries.

src/routes/api/account.ts
import type { APIEvent } from "@solidjs/start/server";
import { readSessionToken } from "own-auth/http";

import { auth } from "~/lib/auth";

export async function GET({ request }: APIEvent) {
  const { token } = readSessionToken(request);
  const current = token ? await auth.getCurrentSession(token) : null;

  if (!current) {
    return Response.json({ error: "Unauthorized" }, { status: 401 });
  }

  const { id, email, name, imageUrl } = current.user;
  return Response.json({ user: { id, email, name, imageUrl } });
}

Add redirect OAuth

Provider configuration belongs in the same auth instance. The mounted route already handles OAuth start and callback requests, so the Solid client only starts the flow and supplies the validated destination.

.env
APP_URL=http://localhost:3000
GOOGLE_CLIENT_ID=your-client-id
GOOGLE_CLIENT_SECRET=your-client-secret
src/lib/auth.ts
import { createOwnAuth } from "own-auth";

const appUrl = process.env.APP_URL!;

export const auth = createOwnAuth({
  tokenPepper: process.env.OWN_AUTH_TOKEN_PEPPER,
  redirectAllowlist: [appUrl],
  oauth: {
    providers: {
      google: {
        clientId: process.env.GOOGLE_CLIENT_ID!,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
        redirectUri: `${appUrl}/api/auth/oauth/google/callback`,
      },
    },
  },
});
src/lib/google-sign-in.ts
import { authClient } from "./auth-client";

export function signInWithGoogle() {
  return authClient.signInWithOAuth({
    provider: "google",
    destination: "/account",
  });
}