npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@contract-kit/provider-auth-better-auth

v0.1.1

Published

Better Auth provider for contract-kit - adds auth port for authentication and session management

Readme

@contract-kit/provider-auth-better-auth

Better Auth provider for Contract Kit - adds authentication and session management via Better Auth.

Overview

This provider wraps an already-configured Better Auth server instance and exposes a typed AuthPort on ctx.ports.auth. It follows the ports & adapters pattern, providing a stable interface for authentication while letting your application own the configuration.

What this provider does:

  • Wraps a Better Auth instance with a simple, stable API
  • Extends ports.auth with getSession, getUser, and requireUser methods
  • Maintains type safety for your custom User type

What this provider does NOT do:

  • Define database schema (you own your user table)
  • Define the User type (you define it in your app)
  • Configure Better Auth (secrets, session strategy, etc. stay in your app)
  • Implement login/signup routes (use Better Auth's routes directly)
  • Manage RBAC/permissions (that's application logic)

Installation

bun add @contract-kit/provider-auth-better-auth better-auth

TypeScript Requirements

This package requires TypeScript 5.0 or higher for proper type inference.

Usage

1. Configure Better Auth in your app

First, set up Better Auth with your database and configuration:

// app/lib/auth.ts
import { betterAuth } from "better-auth";
import { db } from "./db"; // Your Drizzle/Prisma/etc. client

export const auth = betterAuth({
  database: db,
  emailAndPassword: {
    enabled: true,
  },
  // ...other Better Auth configuration
});

// Export your user type
export type AuthUser = typeof auth.$Infer.Session.user;

2. Define your ports type

Add the auth port to your application's ports type:

// app/lib/ports.ts
import type { AuthPort } from "@contract-kit/provider-auth-better-auth";
import type { AuthUser } from "./auth";

export type AppPorts = {
  auth: AuthPort<AuthUser>;
  // ...other ports (db, mailer, eventBus, etc.)
};

3. Wire the provider into Hex

Register the provider when creating your Hex app:

// app/lib/server.ts
import { createServer } from "@contract-kit/server";
import { createAuthBetterAuthProvider } from "@contract-kit/provider-auth-better-auth";
import { auth } from "./auth";
import type { AppPorts } from "./ports";

const basePorts = createPortsBuilder<AppPorts>({});

export const app = createServer({
  ports: basePorts,
  providers: [
    createAuthBetterAuthProvider(auth),
    // ...other providers
  ],
});

4. Use the auth port in middleware

Create middleware to protect routes:

// app/lib/middleware/auth.ts
import type { AppCtx } from "./ctx";

export const ensureAuth = app.router.middleware<AppCtx>(
  async ({ req, ctx, next }) => {
    const user = await ctx.ports.auth.requireUser(req);
    return next({ ...ctx, user });
  },
);

Then use it in your routes:

const protectedRoute = app.router.route({
  method: "GET",
  path: "/api/profile",
  middleware: [ensureAuth],
  handler: async ({ ctx }) => {
    // ctx.user is now available and typed
    return { user: ctx.user };
  },
});

5. Optional: Check auth in use cases

You can also check authentication in use cases:

// app/use-cases/get-user-profile.ts
export const getUserProfile = createUseCase(
  async (ctx: AppCtx, userId: string) => {
    // Get the user from the request (if needed)
    const currentUser = await ctx.ports.auth.getUser(ctx.req);
    
    if (!currentUser) {
      throw new Error("Unauthorized");
    }
    
    // Fetch user profile logic...
  },
);

API Reference

AuthPort<User>

The auth port interface exposed on ctx.ports.auth:

getSession(req: Request): Promise<AuthSession<User> | null>

Get the current session from a Request. Returns null if not authenticated.

const session = await ctx.ports.auth.getSession(req);
if (session) {
  console.log(session.user);
}

getUser(req: Request): Promise<User | null>

Get the current user from a Request. Returns null if not authenticated.

This is a convenience method that extracts the user from the session.

const user = await ctx.ports.auth.getUser(req);
if (user) {
  console.log(user.email);
}

requireUser(req: Request): Promise<User>

Require an authenticated user. Throws an error if not authenticated.

Use this in middleware or use cases that require authentication.

const user = await ctx.ports.auth.requireUser(req);
// user is guaranteed to exist here

Throws: Error with message "[Auth] Unauthorized" if not authenticated.

AuthSession<User>

Represents an authenticated session:

interface AuthSession<User = unknown> {
  user: User;
}

createAuthBetterAuthProvider(auth)

Factory function that creates the provider:

function createAuthBetterAuthProvider<User = unknown>(
  auth: BetterAuthServer<User>
): ServiceProvider

Parameters:

  • auth: A Better Auth server instance configured in your application

Returns: A Contract Kit provider that can be registered with Hex

Advanced Usage

Policy-based authorization

You can integrate the auth port with a policy system based on contract metadata:

// Define a contract with auth metadata
const contract = ContractBuilder()
  .input(z.object({ id: z.string() }))
  .output(z.object({ name: z.string() }))
  .meta({ auth: "required" })
  .build();

// Create middleware that checks the meta
const policyMiddleware = app.router.middleware(async ({ req, ctx, contract, next }) => {
  if (contract.meta?.auth === "required") {
    const user = await ctx.ports.auth.requireUser(req);
    return next({ ...ctx, user });
  }
  return next(ctx);
});

Custom error types

You can wrap requireUser to throw custom error types:

class UnauthorizedError extends Error {
  constructor() {
    super("Unauthorized");
    this.name = "UnauthorizedError";
  }
}

export const requireAuth = async (req: Request) => {
  try {
    return await ctx.ports.auth.requireUser(req);
  } catch (error) {
    throw new UnauthorizedError();
  }
};

Multiple authentication strategies

If you need multiple auth strategies (e.g., JWT + session), you can:

  1. Configure Better Auth with multiple strategies
  2. Or create multiple providers (e.g., createAuthBetterAuthProvider(sessionAuth) + createAuthJWTProvider(jwtAuth))

Better Auth supports multiple strategies out of the box, so the first approach is recommended.

Type Safety

The provider maintains full type safety for your custom User type:

type MyUser = {
  id: string;
  email: string;
  role: "admin" | "user";
};

const authProvider = createAuthBetterAuthProvider<MyUser>(auth);

// Later, in your routes:
const user = await ctx.ports.auth.requireUser(req);
// user.role is typed as "admin" | "user"

Integration with Better Auth Routes

Better Auth provides its own route handlers for login, signup, etc. You can mount these alongside your Contract Kit routes:

// Next.js App Router example
import { auth } from "@/lib/auth";

// Better Auth handles /api/auth/*
export const { GET, POST } = auth.handler;

// Your Contract Kit routes handle /api/app/*
// (mounted separately)

See the Better Auth documentation for details on route configuration.

Examples

Basic setup

import { betterAuth } from "better-auth";
import { createServer } from "@contract-kit/server";
import { createAuthBetterAuthProvider } from "@contract-kit/provider-auth-better-auth";

const auth = betterAuth({ database: db });

const app = createServer({
  ports: basePorts,
  providers: [createAuthBetterAuthProvider(auth)],
});

Protecting routes

const ensureAuth = app.router.middleware(async ({ req, ctx, next }) => {
  const user = await ctx.ports.auth.requireUser(req);
  return next({ ...ctx, user });
});

const protectedRoute = app.router.route({
  method: "GET",
  path: "/api/profile",
  middleware: [ensureAuth],
  handler: async ({ ctx }) => {
    return { user: ctx.user };
  },
});

Optional authentication

const optionalAuthRoute = app.router.route({
  method: "GET",
  path: "/api/data",
  handler: async ({ req, ctx }) => {
    const user = await ctx.ports.auth.getUser(req);
    
    if (user) {
      // Return personalized data
      return { data: getPersonalizedData(user) };
    }
    
    // Return public data
    return { data: getPublicData() };
  },
});

License

MIT