For the complete documentation index, see llms-docs.txt or the site index llms.txt. Full docs corpus: llms-full-docs.txt. Prefer the markdown version of this page at /docs/setup/backend/auth.md. Product capabilities: skill.md. Docs MCP: /docs/mcp. Site MCP: /mcp.

Auth

Set up, configure, and run the Reloop auth microservice locally.

User signup, login, sessions, organizations, and workspace membership via Better Auth.

Overview

PropertyValue
Directoryapps/backend/auth
Port8000
Local URLhttps://local.reloop.sh/api/auth
Swagger UIhttps://local.reloop.sh/api/auth/openapi
StackElysiaJS · Better Auth · PostgreSQL · Redis · NATS

Quick start

bun be:auth:dev

Or start every backend: bun backend:dev / bun dev.

Environment

apps/backend/auth/.env (created/merged by bun setup / bun env:setup from .env.dev).

# Database & Cache
PG_URL=postgresql://reloop:reloop123@localhost:5432/reloop
REDIS_URL=redis://:reloop123@localhost:6379

# Better Auth
BETTER_AUTH_SECRET=tENkVU4GrhckuRw4Bcfh93EWgXOFcszn
BASE_URL="https://local.reloop.sh"
PORT=8000
NODE_ENV=development

# Event Bus
NATS_URL=nats://localhost:4222

# Local dev helpers
DEFAULT_OTP=888888

# Registration controls
DISABLE_SIGNUP=false
DISABLE_ORG_CREATION=false

# OAuth (optional)
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
GITHUB_CLIENT_ID=
GITHUB_CLIENT_SECRET=
VariableRequiredDefaultNotes
PG_URLYESpostgresql://...Shared Postgres from Docker
REDIS_URLYESredis://...Sessions and rate limits
BETTER_AUTH_SECRETYES-Session encryption key
BASE_URLYEShttps://local.reloop.shPublic origin for OAuth / invites
PORTYES8000Host port
NATS_URLYESnats://localhost:4222Event bus
DEFAULT_OTPNo888888Dev OTP bypass
DISABLE_SIGNUPNofalsetrue closes registration on every sign-up path, except pending invitations
DISABLE_ORG_CREATIONNofalsetrue stops users creating further organizations
AUTH_INTERNAL_BASE_URLNoBASE_URLOrigin the services use to validate sessions with each other

Closing registration

DISABLE_SIGNUP=true refuses every path that would create a new account (password sign-up, email code, and social providers) because it is enforced once where the user record is written rather than per method. Existing users keep signing in normally.

The one exception is an address holding a pending, unexpired organization invitation. That keeps an invite-only instance usable: admins go on adding teammates from Members → Invite without reopening registration. An expired invitation does not grant access.

WhoResult with DISABLE_SIGNUP=true
Existing user signing inAllowed
Address with a pending invitationAllowed
Address with an expired invitationRefused
Anyone else, any methodRefused with 403

DISABLE_ORG_CREATION=true is separate, and stops users creating further organizations. Existing organizations, their members and their invitations are untouched, so this is safe to turn on once your organizations exist.

Both flags are read at startup, so restart the service after changing them:

reloop restart auth

[!WARNING] Set DISABLE_SIGNUP=true only after your own account exists. There is no bootstrap exemption: with registration closed and no users, nobody can sign up and no invitation can be issued.

Session validation between services

Every backend validates a session by calling GET /api/auth/v1/get-session on the auth service. It uses BASE_URL for that, which is your public origin, so the call leaves the container, goes back through the reverse proxy and comes in again.

That round trip fails in some proxy setups: TLS terminates at the proxy, the public hostname does not resolve from inside the container network, or the certificate is not trusted internally. fetch throws, the session cannot be read, and protected routes answer 401 straight after a successful login while the login itself worked.

Point the services at the auth container instead:

AUTH_INTERNAL_BASE_URL=http://auth:8000

Then restart the backends. Leave it empty and everything keeps using BASE_URL, so existing deployments are unaffected.

[!NOTE] The path is appended exactly as it is today, so http://auth:8000 becomes http://auth:8000/api/auth/v1/get-session, the same path the proxy forwards to that container. Give the origin only: no trailing path, no trailing slash needed.

This changes only service-to-service validation. BASE_URL stays the public origin and is still what invite links and OAuth callbacks use, so it must remain correct.

Commands

CommandDescription
bun be:auth:devStart dev server with hot reloading
bun run --filter=be-auth buildCompile production bundle
bun run --filter=be-auth startRun compiled production build
bun run --filter=be-auth check-typesTypeScript type-check

Architecture

LayerDetail
SessionsBetter Auth manages login, OAuth, and cookie sessions via Redis + Postgres
Multi-tenancyUsers belong to organizations through member (packages/db/src/schema/auth.ts)
EventsAuth lifecycle events published to NATS under auth.*
First loginUse OTP 888888 locally when DEFAULT_OTP is set

Next

Was this page helpful?

Edit this page