# How simstudioai/sim Uses Better Auth for Shared One-Time Token Verification Across Services

> Learn how simstudioai/sim enhances security using Better Auth for shared one-time token verification across services. Discover its distributed authentication model.

- Repository: [Sim/sim](https://github.com/simstudioai/sim)
- Tags: how-to-guide
- Published: 2026-05-02

---

**The simstudioai/sim repository implements a distributed authentication system where the main Next.js application runs a full Better Auth instance while auxiliary services use a minimal verification-only instance that shares the same PostgreSQL database and secret to validate one-time tokens.**

SimStudio AI's SIM is an open-source project that leverages Better Auth to handle authentication across multiple services. The architecture separates token issuance from token verification, allowing the main application to manage user sessions while specialized services like the realtime server can securely verify identities without duplicating authentication logic.

## The Dual-Instance Architecture

SIM's authentication model relies on two tiers of Better Auth instances. The main Next.js application in `apps/sim` hosts a complete Better Auth configuration capable of user login, session management, password hashing, and OAuth flows.

Auxiliary services, such as the realtime server in `apps/realtime`, instantiate a streamlined version that only handles token verification. This design follows the principle of least privilege—services that merely need to validate tokens do not initialize unnecessary authentication features, reducing the attack surface while maintaining access to the shared `verification` table in PostgreSQL.

## The Verification Helper in [`packages/auth/src/verify.ts`](https://github.com/simstudioai/sim/blob/main/packages/auth/src/verify.ts)

The core of the distributed verification system resides in [`packages/auth/src/verify.ts`](https://github.com/simstudioai/sim/blob/main/packages/auth/src/verify.ts). This file exports the `createVerifyAuth` function, which constructs a minimal Better Auth instance configured specifically for one-time token validation.

The function accepts a `VerifyAuthOptions` interface containing the shared secret and base URL:

```typescript
export interface VerifyAuthOptions {
  /** Better Auth shared secret. Must match the apps/sim Better Auth secret. */
  secret: string
  /** Public-facing Better Auth URL (usually same as NEXT_PUBLIC_APP_URL). */
  baseURL: string
}

```

When invoked, `createVerifyAuth` returns a Better Auth instance configured with the `oneTimeToken` plugin set to expire tokens after 24 hours:

```typescript
export function createVerifyAuth(options: VerifyAuthOptions) {
  return betterAuth({
    baseURL: options.baseURL,
    secret: options.secret,
    database: drizzleAdapter(db, {
      provider: 'pg',
      schema,
    }),
    plugins: [
      // Enables creation & verification of short-lived one-time tokens
      oneTimeToken({ expiresIn: 24 * 60 * 60 }), // 24 h
    ],
  })
}

```

## One-Time Token Flow

The authentication flow spans multiple services but remains unified through the shared database schema. The process operates in five distinct stages.

**Token Issuance**: The main application in `apps/sim` generates a one-time token using its full Better Auth instance. The `oneTimeToken` plugin stores this token in the shared `verification` table with a 24-hour expiration timestamp.

**Client Distribution**: The client receives this token, typically via URL query parameters or response headers, and presents it when connecting to auxiliary services.

**Service Initialization**: Services like the realtime server initialize their auth client by importing `createVerifyAuth` from `@sim/auth/verify` and passing the required environment variables:

```typescript
import { createVerifyAuth } from '@sim/auth/verify'

const realtimeAuth = createVerifyAuth({
  secret: process.env.BETTER_AUTH_SECRET!, // must match apps/sim
  baseURL: process.env.NEXT_PUBLIC_APP_URL!,
})

```

**Token Verification**: The service verifies the token using the `verify` method. The minimal instance checks the token against the shared PostgreSQL database, validating both its existence and expiration status.

**Session Establishment**: Upon successful verification, the service extracts the user ID from the token payload and establishes an authenticated session for the request.

## Database Schema and Shared Configuration

Consistency across services depends on shared infrastructure. The `BETTER_AUTH_SECRET` environment variable must remain identical between the main application and all auxiliary services. This secret enables HMAC validation of tokens across the distributed system.

The database schema, defined in [`packages/db/schema.ts`](https://github.com/simstudioai/sim/blob/main/packages/db/schema.ts), provides the common structure for the `verification` table. Both the full and minimal Better Auth instances connect to this shared PostgreSQL schema using the Drizzle adapter, ensuring that tokens created by `apps/sim` are immediately visible to `apps/realtime` and other services.

## Implementation Example in the Realtime Server

The realtime server in [`apps/realtime/src/auth.ts`](https://github.com/simstudioai/sim/blob/main/apps/realtime/src/auth.ts) demonstrates practical implementation of this pattern. The service imports `createVerifyAuth` to instantiate its auth client, then uses it within request handlers to validate incoming tokens.

When handling WebSocket connections, the server extracts bearer tokens from authorization headers and delegates verification to the Better Auth instance:

```typescript
export async function handleWsConnection(req) {
  const token = req.headers.get('authorization')?.replace('Bearer ', '')
  if (!token) throw new Error('Missing auth token')

  const { success, data } = await realtimeAuth.verify(token)
  if (!success) throw new Error('Invalid or expired token')

  // `data` now holds the user ID and any extra payload stored in the token
  const userId = data.userId
  // …continue handling the WebSocket connection as an authenticated user
}

```

This approach allows the realtime server to authenticate users without maintaining its own user database or session management logic.

## Summary

- **Dual-instance architecture**: The main Next.js application runs full Better Auth while auxiliary services use minimal verification-only instances from [`packages/auth/src/verify.ts`](https://github.com/simstudioai/sim/blob/main/packages/auth/src/verify.ts).
- **Shared infrastructure**: All services connect to the same PostgreSQL database schema and use identical `BETTER_AUTH_SECRET` environment variables.
- **One-time token pattern**: The `oneTimeToken` plugin creates 24-hour expiring tokens stored in the shared `verification` table.
- **Least-privilege design**: Auxiliary services initialize only the Better Auth features required for token verification, reducing security exposure.
- **Cross-service compatibility**: Tokens generated in `apps/sim` validate seamlessly in `apps/realtime` and other services through the `createVerifyAuth` helper.

## Frequently Asked Questions

### How does the minimal Better Auth instance in auxiliary services access user data?

The minimal instance configured via `createVerifyAuth` connects to the same PostgreSQL database as the main application using the Drizzle adapter. Because both instances share the database schema defined in [`packages/db/schema.ts`](https://github.com/simstudioai/sim/blob/main/packages/db/schema.ts), the minimal instance can read the `verification` table and validate tokens without duplicating user data storage.

### What happens if the BETTER_AUTH_SECRET differs between services?

Token verification will fail. The `createVerifyAuth` function requires the same `secret` parameter used by the main application's Better Auth instance. This secret is used for cryptographic validation of token signatures. If auxiliary services use a different secret, HMAC validation fails and the `verify` method returns an unsuccessful result.

### Can auxiliary services generate tokens or only verify them?

Auxiliary services using the `createVerifyAuth` helper can only verify tokens. The minimal instance configuration in [`packages/auth/src/verify.ts`](https://github.com/simstudioai/sim/blob/main/packages/auth/src/verify.ts) exclusively includes the `oneTimeToken` plugin for verification purposes. Token generation requires the full Better Auth instance running in `apps/sim` with complete session management and user authentication capabilities.

### How long do one-time tokens remain valid?

Tokens expire after 24 hours according to the configuration in [`packages/auth/src/verify.ts`](https://github.com/simstudioai/sim/blob/main/packages/auth/src/verify.ts). The `oneTimeToken` plugin is initialized with `expiresIn: 24 * 60 * 60` seconds. After this period, the verification method automatically rejects the token as expired, requiring the user to obtain a new token from the main application.