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

@opensaas/stack-auth

v0.20.0

Published

Better-auth integration for OpenSaas Stack

Readme

@opensaas/stack-auth

Better-auth integration for OpenSaas Stack - Add authentication to your app in minutes.

Features

  • Auto-generated Auth Tables - User, Session, Account, and Verification lists created automatically
  • Session Integration - Seamless integration with OpenSaas access control system
  • Pre-built UI Components - Sign in, sign up, and forgot password forms ready to use
  • Multiple Auth Methods - Email/password, OAuth providers (GitHub, Google, etc.)
  • Email Verification - Built-in email verification support
  • Password Reset - Forgot password flow included
  • Type-Safe - Full TypeScript support with automatic type generation
  • Configurable Sessions - Choose which user fields to include in session

Installation

pnpm add @opensaas/stack-auth better-auth

Quick Start

1. Update Your Config

Wrap your OpenSaas config with withAuth():

// opensaas.config.ts
import { config } from '@opensaas/stack-core'
import { withAuth, authConfig } from '@opensaas/stack-auth'

export default withAuth(
  config({
    db: {
      provider: 'sqlite',
      url: process.env.DATABASE_URL || 'file:./dev.db',
    },
    lists: {
      // Your custom lists here
    },
  }),
  authConfig({
    emailAndPassword: { enabled: true },
    emailVerification: { enabled: true },
    passwordReset: { enabled: true },
    sessionFields: ['userId', 'email', 'name'],
  }),
)

2. Generate Schema and Push to Database

pnpm generate  # Generates Prisma schema with auth tables
pnpm db:push   # Push schema to database

3. Create Auth Server Instance

// lib/auth.ts
import { createAuth } from '@opensaas/stack-auth/server'
import config from '../opensaas.config'

export const auth = createAuth(config)

// Export auth API for route handlers
export const GET = auth.handler
export const POST = auth.handler

4. Set Up Auth Route

// app/api/auth/[...all]/route.ts
export { GET, POST } from '@/lib/auth'

5. Create Auth Client

// lib/auth-client.ts
'use client'

import { createClient } from '@opensaas/stack-auth/client'

export const authClient = createClient({
  baseURL: process.env.NEXT_PUBLIC_APP_URL || 'http://localhost:3000',
})

6. Add Sign In Page

// app/sign-in/page.tsx
import { SignInForm } from '@opensaas/stack-auth/ui'
import { authClient } from '@/lib/auth-client'

export default function SignInPage() {
  return (
    <div className="min-h-screen flex items-center justify-center">
      <SignInForm authClient={authClient} redirectTo="/admin" />
    </div>
  )
}

7. Use Session in Access Control

Sessions are now automatically available in your access control functions:

// opensaas.config.ts
import { withAuth, authConfig } from '@opensaas/stack-auth'

export default withAuth(
  config({
    lists: {
      Post: list({
        fields: { title: text(), content: text() },
        access: {
          operation: {
            // Session is automatically populated from better-auth
            create: ({ session }) => !!session,
            update: ({ session, item }) => {
              if (!session) return false
              return { authorId: { equals: session.userId } }
            },
          },
        },
      }),
    },
  }),
  authConfig({
    emailAndPassword: { enabled: true },
    // Session will contain: { userId, email, name }
    sessionFields: ['userId', 'email', 'name'],
  }),
)

Configuration

Auth Config Options

authConfig({
  // Email/password authentication
  emailAndPassword: {
    enabled: true,
    minPasswordLength: 8,
    requireConfirmation: true,
  },

  // Email verification
  emailVerification: {
    enabled: true,
    sendOnSignUp: true,
    tokenExpiration: 86400, // 24 hours in seconds
  },

  // Password reset
  passwordReset: {
    enabled: true,
    tokenExpiration: 3600, // 1 hour in seconds
  },

  // OAuth providers
  socialProviders: {
    github: {
      clientId: process.env.GITHUB_CLIENT_ID!,
      clientSecret: process.env.GITHUB_CLIENT_SECRET!,
    },
    google: {
      clientId: process.env.GOOGLE_CLIENT_ID!,
      clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
    },
  },

  // Session configuration
  session: {
    expiresIn: 604800, // 7 days in seconds
    updateAge: true, // Refresh session on each request
  },

  // Fields to include in session object
  sessionFields: ['userId', 'email', 'name', 'role'],

  // Extend User list with custom fields
  extendUserList: {
    fields: {
      role: text({ defaultValue: 'user' }),
      company: text(),
    },
  },

  // Custom email sending function
  sendEmail: async ({ to, subject, html }) => {
    await yourEmailService.send({ to, subject, html })
  },
})

UI Components

SignInForm

import { SignInForm } from '@opensaas/stack-auth/ui'
import { authClient } from '@/lib/auth-client'

<SignInForm
  authClient={authClient}
  redirectTo="/dashboard"
  showSocialProviders={true}
  socialProviders={['github', 'google']}
  onSuccess={() => console.log('Signed in!')}
  onError={(error) => console.error(error)}
/>

SignUpForm

import { SignUpForm } from '@opensaas/stack-auth/ui'
import { authClient } from '@/lib/auth-client'

<SignUpForm
  authClient={authClient}
  redirectTo="/dashboard"
  requirePasswordConfirmation={true}
  onSuccess={() => console.log('Account created!')}
/>

ForgotPasswordForm

import { ForgotPasswordForm } from '@opensaas/stack-auth/ui'
import { authClient } from '@/lib/auth-client'

<ForgotPasswordForm
  authClient={authClient}
  onSuccess={() => console.log('Reset email sent!')}
/>

Auto-Generated Lists

The following lists are automatically created when you use withAuth():

User

{
  id: string
  name: string
  email: string (unique)
  emailVerified: boolean
  image?: string
  sessions: Session[] (relationship)
  accounts: Account[] (relationship)
  createdAt: Date
  updatedAt: Date
  // Plus any custom fields you add via extendUserList
}

Session

{
  id: string
  token: string (unique)
  userId: string
  expiresAt: Date
  ipAddress?: string
  userAgent?: string
  user: User (relationship)
  createdAt: Date
  updatedAt: Date
}

Account

{
  id: string
  userId: string
  accountId: string
  providerId: string ('github', 'google', 'credentials', etc.)
  accessToken?: string
  refreshToken?: string
  accessTokenExpiresAt?: Date
  refreshTokenExpiresAt?: Date
  scope?: string
  idToken?: string
  password?: string
  user: User (relationship)
  createdAt: Date
  updatedAt: Date
}

Verification

{
  id: string
  identifier: string (e.g., email address)
  value: string (token)
  expiresAt: Date
  createdAt: Date
  updatedAt: Date
}

Extending the User List

Add custom fields to the User model:

authConfig({
  extendUserList: {
    fields: {
      role: select({
        options: [
          { label: 'User', value: 'user' },
          { label: 'Admin', value: 'admin' },
        ],
        defaultValue: 'user',
      }),
      company: text(),
      phoneNumber: text(),
    },
    // Optionally override access control
    access: {
      operation: {
        query: () => true,
        create: () => true,
        update: ({ session, item }) => session?.userId === item?.id,
        delete: ({ session }) => session?.role === 'admin',
      },
    },
  },
})

Session Fields

Control which user fields are included in the session object:

authConfig({
  sessionFields: ['userId', 'email', 'name', 'role'],
})

// Now in access control:
access: {
  operation: {
    create: ({ session }) => {
      console.log(session) // { userId, email, name, role }
      return session?.role === 'admin'
    }
  }
}

Client-Side Hooks

Use better-auth hooks in your React components:

'use client'

import { authClient } from '@/lib/auth-client'

function MyComponent() {
  const { data: session, isPending } = authClient.useSession()

  if (isPending) return <div>Loading...</div>

  if (!session) return <div>Not signed in</div>

  return <div>Welcome, {session.user.name}!</div>
}

Server-Side Session

Access the session in server components or actions:

import { getContext } from '@/.opensaas/context'

async function myServerAction() {
  const context = await getContext()

  if (!context.session) {
    throw new Error('Not authenticated')
  }

  // Session contains fields you specified in sessionFields
  console.log(context.session) // { userId, email, name }

  // Use context.db with access control
  const posts = await context.db.post.findMany()
}

Environment Variables

# .env
DATABASE_URL=file:./dev.db

# OAuth providers (optional)
GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret
GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

# Better-auth
BETTER_AUTH_SECRET=your_secret_key  # Generate with: openssl rand -base64 32
BETTER_AUTH_URL=http://localhost:3000
NEXT_PUBLIC_APP_URL=http://localhost:3000

Examples

See examples/auth-demo for a complete working example.

How It Works

  1. withAuth() merges auth lists (User, Session, Account, Verification) with your config
  2. Generator creates Prisma schema with all auth tables
  3. Session Provider uses better-auth to get current session
  4. Context includes session automatically in all access control functions
  5. UI Components provide ready-to-use auth forms

License

MIT