vs3

Authentication

Protect your storage API with authentication using createAuthMiddleware and plug in any auth provider.

createAuthMiddleware is a standalone middleware that lets you add authentication to your storage API. It works independently from request signing, so you can use it on its own or combine it with signature verification, rate limiting, and other middlewares.

Quick start

Pass an AuthHandler function that checks the request and returns whether the user is authenticated. The middleware adds auth (with userId and optional metadata) to the middleware chain context.

import { createStorage, createAuthMiddleware } from "vs3";

const storage = createStorage({
  bucket: "my-bucket",
  adapter: myAdapter,
  middlewares: [
    createAuthMiddleware({
      handler: async ({ request, headers }) => {
        const token = headers["authorization"]?.replace("Bearer ", "");
        if (!token) {
          return { authenticated: false, reason: "Missing token" };
        }

        // Validate the token with your auth provider
        const session = await verifyToken(token);
        if (!session) {
          return { authenticated: false, reason: "Invalid token" };
        }

        return {
          authenticated: true,
          session: { userId: session.userId, metadata: { role: session.role } },
        };
      },
    }),
  ],
});

When authentication succeeds, { auth: { userId, metadata } } is added to the context. When it fails, the middleware throws a StorageServerError with code UNAUTHORIZED.

Writing an auth handler

An AuthHandler receives a context object with request (the raw Request) and headers (a plain Record<string, string>). It returns an AuthResult:

import type { AuthHandler } from "vs3";

// Successful authentication — userId is required
{ authenticated: true, session: { userId: "user-123", metadata: { role: "admin" } } }

// Failed authentication — reason is optional
{ authenticated: false, reason: "Token expired" }

The handler can be synchronous or async.

Using with third-party auth providers

Because the handler is a plain function, you can integrate any auth provider. vs3 provides dedicated middlewares for the following providers:

These integrations are just a convenience wrapper around the createAuthMiddleware function - they can still be configured like any other middleware.

better-auth

The better-auth middleware uses the incoming headers from the request and passes them to the better-auth instance to get the session. If no session is found, the middleware returns { authenticated: false } and the request is aborted.

import { betterAuth } from "vs3/middleware/auth";
import { auth } from "./auth"; // your better-auth instance

export const storage = createStorage({
  middlewares: [
    betterAuth({
      auth,
    }),
  ]
})

Take a look at the better-auth example for a complete implementation.

Path filtering

Use skipPaths to skip authentication on certain paths, or includePaths to only run authentication on specific paths. These are mutually exclusive.

createAuthMiddleware({
  handler: myAuthHandler,
  // Skip auth for health checks
  skipPaths: ["/health"],
});

createAuthMiddleware({
  handler: myAuthHandler,
  // Only require auth for upload
  includePaths: ["/upload-url"],
});

Custom failure handling

By default the middleware throws a StorageServerError with code UNAUTHORIZED. You can override this with onAuthFailure:

createAuthMiddleware({
  handler: myAuthHandler,
  onAuthFailure: (reason, request) => {
    return new Response(JSON.stringify({ error: reason }), {
      status: 401,
      headers: { "content-type": "application/json" },
    });
  },
});

If onAuthFailure returns a Response, that response is thrown and stops the middleware chain. If it returns nothing, the default StorageServerError is thrown.

Combining with other middlewares

The auth middleware works like any other vs3 middleware. Place it in the middlewares array in the order you want it to run:

import {
  createStorage,
  createAuthMiddleware,
  createVerifySignatureMiddleware,
  createRateLimitMiddleware,
  createInMemoryRateLimitStore,
} from "vs3";

const storage = createStorage({
  bucket: "my-bucket",
  adapter: myAdapter,
  middlewares: [
    createVerifySignatureMiddleware({ secret: process.env.SIGNING_SECRET! }),
    createAuthMiddleware({ handler: myAuthHandler }),
    createRateLimitMiddleware({
      maxRequests: 100,
      windowMs: 60_000,
      store: createInMemoryRateLimitStore(),
    }),
  ],
});

API reference

createAuthMiddleware()

Creates an authentication middleware instance.

Config optionTypeDescription
handlerAuthHandlerRequired. Function that checks authentication and returns an AuthResult.
skipPathsstring[]Paths to skip authentication for. Mutually exclusive with includePaths.
includePathsstring[]Paths to require authentication on. Mutually exclusive with skipPaths.
onAuthFailure(reason: string, request: Request) => Response | neverCustom handler for authentication failures.

Types

TypeDescription
AuthHandler(context: AuthHandlerContext) => AuthResult | Promise<AuthResult>
AuthHandlerContext{ request: Request; headers: Record<string, string> }
AuthResult{ authenticated: true; session: AuthSession } or { authenticated: false; reason?: string }
AuthSession{ userId: string; metadata?: Record<string, unknown> }
AuthMiddlewareConfigConfig object passed to createAuthMiddleware.
AuthMiddlewareResult{ auth: { userId: string; metadata?: Record<string, unknown> } } — added to the middleware chain context.

All types are exported from the vs3 package.

On this page