Shared authentication for Digital Crew: Better Auth on Postgres (Neon, RDS, local, etc.) with Drizzle ORM. Tenancy is modeled as organizations (workspaces): users can belong to several orgs, and the session stores an active organization for permissions and for scoping your product data (organizationId on your own tables).
Use the same package for a private orchestrator, managed agent SaaS (shared DB + cookies), or open-source self-hosted agents (each deployment still uses its own database and secrets — that is deployment isolation, not org tenancy).
- Node.js 20+
- A Postgres URL for the auth database (see env vars below)
- npm (or pnpm / yarn)
| Path | Purpose |
|---|---|
src/ |
Source (Better Auth config, Drizzle schema, env helpers) |
src/auth.cli.ts |
Minimal config for the Better Auth schema generator CLI only |
drizzle/ |
SQL migrations from Drizzle Kit |
npm install
npm run buildPoint at the repo root. Pin a branch, tag, or commit for reproducible installs.
{
"dependencies": {
"@digitalcrew/auth": "git+https://github.com/Digital-Crew-Technologies/digitalcrew-auth.git#main"
}
}Other valid forms:
"@digitalcrew/auth": "git+https://github.com/Digital-Crew-Technologies/digitalcrew-auth.git#v0.1.0""@digitalcrew/auth": "github:Digital-Crew-Technologies/digitalcrew-auth#main"On install, npm runs the prepare script (npm run build), which compiles TypeScript to dist/ (because dist/ is not committed). If you use npm install --ignore-scripts, run npm run build inside node_modules/@digitalcrew/auth yourself, or depend on a published package that ships dist/.
Copy .env.example to .env and set values. They are validated with Zod when you call loadAuthEnv() or getAuth().
| Variable | Required | Description |
|---|---|---|
DIGITALCREW_AUTH_DATABASE_URL |
Yes | Postgres URL used only by @digitalcrew/auth. loadAuthEnv() exposes the same value as DATABASE_URL on the returned object for Drizzle / Better Auth. |
DIGITALCREW_AUTH_SECRET |
Yes | At least 32 characters. Generate with npx auth@latest secret |
DIGITALCREW_AUTH_URL |
Yes | Public origin of the app that serves the auth API (e.g. https://app.example.com) |
DIGITALCREW_AUTH_TRUSTED_ORIGINS |
No | Comma-separated extra origins (CORS / CSRF). DIGITALCREW_AUTH_URL is always included |
GOOGLE_CLIENT_ID / GOOGLE_CLIENT_SECRET |
Optional pair | Omit both, or set both, for Google sign-in |
DIGITALCREW_AUTH_COOKIE_DOMAIN |
No | Production SSO across subdomains: root domain with a leading dot, e.g. .example.com |
NODE_ENV |
No | development / production / test — affects secure cookies in production |
AUTH_MAX_ORGANIZATIONS_PER_USER |
No | Cap how many organizations a user may create (omit = unlimited) |
AUTH_MAX_MEMBERS_PER_ORGAN |
No | Member limit per org (default 100 if omitted) |
AUTH_ALLOW_USER_CREATE_ORGAN |
No | true or false — whether end users can create orgs (default true). Set false if only your orchestrator creates tenants |
After npm install / pnpm install so @digitalcrew/auth is present, use one of:
npm exec digitalcrew-auth migrateTo run the binary via npx, you must tell npx which package provides it (only works if @digitalcrew/auth is installable from the registry or another source npx understands):
npx --package @digitalcrew/auth digitalcrew-auth migrateFrom the repository root (with DIGITALCREW_AUTH_DATABASE_URL set):
npm run db:migrateTo create a new migration after you change Drizzle schema files:
npm run db:generateReview the new files under drizzle/, then run db:migrate against your database.
When you add or remove Better Auth plugins, mirror the same options in src/auth.cli.ts, then run:
npm run auth:generateThat overwrites src/schema/auth.ts. After that, run npm run db:generate and migrate as needed.
-
createAuth(env)— Builds an auth instance from an explicitDigitalcrewAuthEnv(fromloadAuthEnv(customProcessEnv)or your own object). Use this for tests, scripts, or whenprocess.envis not the source of truth. -
getAuth()— Singleton that callsloadAuthEnv(process.env). Convenient for a single deployed app.
Both return a Better Auth instance with .handler (Web Request → Response) and .api (programmatic endpoints).
Mount the handler on a catch-all route so Better Auth can own /api/auth/*.
app/api/auth/[...all]/route.ts:
import { getAuth } from "@digitalcrew/auth";
import { routeHandlers } from "@digitalcrew/auth/http";
const auth = getAuth();
export const { GET, POST } = routeHandlers(auth);Set DIGITALCREW_AUTH_URL to the same origin as this app (including port in development).
Pass the incoming request headers (cookies) into Better Auth’s session API. Example for Next.js:
import { getAuth } from "@digitalcrew/auth";
import { headers } from "next/headers";
const auth = getAuth();
const session = await auth.api.getSession({
headers: await headers(),
});
if (!session) {
// unauthenticated
}Adapt headers to your framework (Request, Hono, etc.) — the important part is forwarding Cookie.
Use the auth client with the same base URL as DIGITALCREW_AUTH_URL (the app that exposes /api/auth/*).
import { createDigitalcrewAuthClient } from "@digitalcrew/auth";
export const authClient = createDigitalcrewAuthClient(
process.env.NEXT_PUBLIC_APP_URL!,
);Use Better Auth’s client methods (for example authClient.signIn.email, authClient.signOut, social methods) as documented in the Better Auth client docs.
- Tenant = organization row (
organizationtable). Members are inmember; invites useinvitation. - After sign-in, the user should select or create an org (or your server creates one). The session exposes
activeOrganizationId(also on thesessiontable in Drizzle) for “current workspace”. - Client:
createDigitalcrewAuthClientincludes the organization plugin — useauthClient.organization.*(create, list, set active org, invite members, etc.). See Organization plugin. - Server:
auth.apiexposes the same capabilities withheadersfor session context. - Your product tables (campaigns, agents, entitlements, …) should include an
organizationId(or equivalent) foreign key and enforce it in queries using the active org id fromgetSession(fieldactiveOrganizationIdon the session payload — see Better Auth types in your editor).
Advanced: import digitalcrewOrganization from @digitalcrew/auth if you need to compose the same plugin options elsewhere (keep behavior aligned with createAuth).
If the same app needs raw queries against the auth database:
import { getDb, loadAuthEnv } from "@digitalcrew/auth";
const db = getDb(loadAuthEnv());Prefer extending schema in this package and exporting tables from @digitalcrew/auth when you add product-specific tables next to auth.
| Script | Description |
|---|---|
npm run build |
Compile @digitalcrew/auth to dist/ |
npm run auth:generate |
Regenerate Drizzle schema from auth.cli.ts |
npm run db:generate |
Generate a new Drizzle migration |
npm run db:migrate |
Apply migrations |
- Never commit
.envor real secrets. - Use a strong, random
DIGITALCREW_AUTH_SECRETin every environment. - In production, serve over HTTPS and keep
NODE_ENV=productionso cookies usesecure: true.
This project builds on Better Auth (MIT). The full copyright and permission notice for that dependency is in NOTICE at the repository root. When you redistribute this package or vendor its dependencies, preserve the license files shipped under node_modules/better-auth as well.