Skip to content
Back to skills

Solidstart Middleware Auth

ASecurity

SolidStart middleware, sessions, authentication: createMiddleware with onRequest/onBeforeResponse, useSession for cookies, protected routes, WebSocket endpoints.

  • 57 stars
  • 0 votes
  • 0 copies
  • 1 view
  • Added October 17, 2026
businesstypescriptgoapidatabase

Works with

  • cli
  • api

Security analysis

A100/100

Scanned October 17, 2026

npx -y skills add diegosouzapw/awesome-omni-skill --skill solidstart-middleware-auth --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Solidstart Middleware Auth?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Solidstart Middleware Auth
[![Security: A — Skills Directory](https://www.skillsdirectory.com/api/skills/diegosouzapw-solidstart-middleware-auth/badge)](https://www.skillsdirectory.com/skills/diegosouzapw-solidstart-middleware-auth)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: solidstart-middleware-auth
description: "SolidStart middleware, sessions, authentication: createMiddleware with onRequest/onBeforeResponse, useSession for cookies, protected routes, WebSocket endpoints."
metadata:
  globs:
    - "src/middleware/**/*"
    - "**/*auth*"
    - "**/*session*"
---

# SolidStart Middleware, Sessions & Auth

## Middleware

Configure in `app.config.ts`:
```ts
export default defineConfig({
  middleware: "src/middleware/index.ts"
});
```

### When to Use Middleware (and When Not To)

Use middleware for:
- Request/response header management (CSP, cache control, CORS)
- Request-scoped data via `event.locals` (trace IDs, feature flags, auth hints)
- Early redirects (legacy URLs, locale routing, maintenance mode)
- Lightweight preprocessing (normalizing headers, basic validation)

Avoid middleware for:
- Authorization (middleware does not run on every client-side navigation)
- Heavy computation or database calls (keep it fast and side-effect light)
- Business logic that depends on user state (put checks in queries/actions/API)

### Basic Middleware

```ts
// src/middleware/index.ts
import { createMiddleware } from "@solidjs/start/middleware";

export default createMiddleware({
  onRequest: (event) => {
    console.log("Request:", event.request.url);
    event.locals.startTime = Date.now();
  },
  onBeforeResponse: (event) => {
    const duration = Date.now() - event.locals.startTime;
    console.log(`Request took ${duration}ms`);
  }
});
```

### Lifecycle Events

- `onRequest`: Before route handler (modify headers, store in locals)
- `onBeforeResponse`: After route handler (modify response, logging)

Returning a `Response` from either hook short-circuits the pipeline and skips
remaining middleware and route handlers.

```ts
export default createMiddleware({
  onRequest: (event) => {
    event.locals.userAgent = event.request.headers.get("user-agent");
  },
  onBeforeResponse: (event) => {
    event.response.headers.set("X-Response-Time", "100ms");
  }
});
```

### Accessing Locals

Store in middleware, access with `getRequestEvent`:

```ts
// middleware/index.ts
export default createMiddleware({
  onRequest: (event) => {
    event.locals.user = { id: "123", name: "John" };
  }
});

// In server function
import { getRequestEvent } from "solid-js/web";

const getUser = query(async () => {
  "use server";
  const event = getRequestEvent();
  return event?.locals?.user;
}, "user");
```

### Typing Locals

```ts
// global.d.ts
/// <reference types="@solidjs/start/env" />
declare module "App" {
  interface RequestEventLocals {
    user?: { id: string; name: string };
    startTime?: number;
  }
}
```

### Headers & Cookies

```ts
import { getCookie, setCookie } from "vinxi/http";

export default createMiddleware({
  onRequest: (event) => {
    // Read headers
    const userAgent = event.request.headers.get("user-agent");
    
    // Set headers
    event.request.headers.set("x-custom-header", "value");
    event.response.headers.set("Cache-Control", "max-age=3600");
    
    // Cookies
    const theme = getCookie(event.nativeEvent, "theme");
    setCookie(event.nativeEvent, "session", "abc123", {
      httpOnly: true,
      secure: true,
      maxAge: 60 * 60 * 24
    });
  }
});
```

### Custom Responses & Short-Circuiting

Only `Response` objects can be returned from middleware.

```ts
import { json, redirect } from "@solidjs/router";

export default createMiddleware({
  onRequest: (event) => {
    const { pathname } = new URL(event.request.url);
    if (pathname === "/old-path") {
      return redirect("/new-path", 301);
    }

    const authHeader = event.request.headers.get("Authorization");
    if (!authHeader) {
      return json({ error: "Unauthorized" }, { status: 401 });
    }
  }
});
```

### Building Custom Middleware (Composable Functions)

Prefer small, focused functions and compose them in order.

```ts
import type { FetchEvent } from "@solidjs/start/server";

function withRequestId() {
  return (event: FetchEvent) => {
    const id = crypto.randomUUID();
    event.locals.requestId = id;
    event.response.headers.set("x-request-id", id);
  };
}

function withTiming() {
  return (event: FetchEvent) => {
    event.locals.startTime = Date.now();
  };
}

function withTimingResponse() {
  return (event: FetchEvent) => {
    const ms = Date.now() - event.locals.startTime;
    event.response.headers.set("x-response-time", `${ms}ms`);
  };
}

export default createMiddleware({
  onRequest: [withRequestId(), withTiming()],
  onBeforeResponse: [withTimingResponse()]
});
```

### Extending Existing Middleware

Export shared middleware arrays, then extend them in route-specific configs.

```ts
// src/middleware/base.ts
import type { FetchEvent } from "@solidjs/start/server";

export const baseOnRequest: Array<(e: FetchEvent) => void> = [
  (event) => {
    event.locals.userAgent = event.request.headers.get("user-agent");
  }
];
```

```ts
// src/middleware/index.ts
import { createMiddleware } from "@solidjs/start/middleware";
import { baseOnRequest } from "./base";
import type { FetchEvent } from "@solidjs/start/server";

function withMaintenanceMode() {
  return (event: FetchEvent) => {
    if (process.env.MAINTENANCE === "true") {
      return new Response("Maintenance", { status: 503 });
    }
  };
}

export default createMiddleware({
  onRequest: [...baseOnRequest, withMaintenanceMode()]
});
```

### Redirects & Responses

```ts
import { redirect, json } from "@solidjs/router";

export default createMiddleware({
  onRequest: (event) => {
    const { pathname } = new URL(event.request.url);
    if (pathname === "/old-path") {
      return redirect("/new-path", 301);
    }
    
    const authHeader = event.request.headers.get("Authorization");
    if (!authHeader) {
      return json({ error: "Unauthorized" }, { status: 401 });
    }
  }
});
```

### Chaining Middleware

```ts
function middleware1(event: FetchEvent) {
  event.request.headers.set("x-header1", "value1");
}

function middleware2(event: FetchEvent) {
  event.request.headers.set("x-header2", "value2");
}

export default createMiddleware({
  onRequest: [middleware1, middleware2]
});
```

Order matters: middleware runs in array order; dependencies must come first.

## Sessions

### Basic Session

```ts
// src/lib/session.ts
import { useSession } from "vinxi/http";

type SessionData = {
  userId?: string;
  theme?: "light" | "dark";
};

export async function useAppSession() {
  "use server";
  const session = await useSession<SessionData>({
    password: process.env.SESSION_SECRET!, // Must be 32+ chars
    name: "app-session"
  });
  return session;
}

// Generate: openssl rand -base64 32
```

### Session Operations

```ts
// Get session data
export async function getSessionUserId() {
  "use server";
  const session = await useAppSession();
  return session.data.userId;
}

// Update session
export async function updateSession(updates: Partial<SessionData>) {
  "use server";
  const session = await useAppSession();
  await session.update(updates);
}

// Clear session
export async function clearSession() {
  "use server";
  const session = await useAppSession();
  await session.clear();
}
```

### Using with Queries

```ts
const getCurrentUser = query(async () => {
  "use server";
  const session = await useAppSession();
  if (!session.data.userId) {
    throw redirect("/login");
  }
  return await db.getUser(session.data.userId);
}, "currentUser");
```

## Authentication

### Protected Routes

```tsx
// routes/admin.tsx
import { query, redirect, createAsync } from "@solidjs/router";

const getAdminData = query(async () => {
  "use server";
  const session = await useAppSession();
  if (!session.data.userId) {
    throw redirect("/login");
  }
  
  const user = await db.getUser(session.data.userId);
  if (!user.isAdmin) {
    throw redirect("/");
  }
  
  return await db.getAdminData();
}, "adminData");

export default function AdminPage() {
  const data = createAsync(() => getAdminData(), { deferStream: true });
  return <div>{data()}</div>;
}
```

**Important:** Use `deferStream: true` - server-side redirects can't occur after streaming starts.

### Login Action

```tsx
const loginAction = action(async (formData: FormData) => {
  "use server";
  const email = formData.get("email")?.toString();
  const password = formData.get("password")?.toString();
  
  const user = await db.verifyUser(email, password);
  if (!user) {
    return { error: "Invalid credentials" };
  }
  
  const session = await useAppSession();
  await session.update({ userId: user.id });
  
  throw redirect("/dashboard");
}, "login");
```

### Logout Action

```tsx
const logoutAction = action(async () => {
  "use server";
  const session = await useAppSession();
  await session.clear();
  throw redirect("/");
}, "logout");
```

## WebSocket Endpoints

**Experimental** - use with caution.

### Configuration

```ts
// app.config.ts
export default defineConfig({
  server: {
    experimental: { websocket: true }
  }
}).addRouter({
  name: "ws",
  type: "http",
  handler: "./src/ws.ts",
  target: "server",
  base: "/ws"
});
```

### WebSocket Handler

```ts
// src/ws.ts
import { eventHandler } from "vinxi/http";

export default eventHandler({
  handler() {},
  websocket: {
    async open(peer) {
      console.log("Connection:", peer.id);
    },
    async message(peer, msg) {
      const message = msg.text();
      peer.send(message); // Broadcast
    },
    async close(peer, details) {
      console.log("Closed:", peer.id);
    },
    async error(peer, error) {
      console.error("Error:", error);
    }
  }
});
```

## Best Practices

1. Keep middleware lightweight - no heavy computation
2. Use sessions for auth state - secure, encrypted cookies
3. Use `deferStream: true` for protected routes
4. Type your locals for better TypeScript support
5. Don't rely on middleware for authorization (doesn't run on all requests)
6. Return a `Response` only when you want to terminate the pipeline
7. Compose small middleware functions and keep ordering explicit

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…

Newsletter

Get the weekly list of safe new skills, and the ones we pulled.