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
| Property | Value |
|---|---|
| Directory | apps/backend/auth |
| Port | 8000 |
| Local URL | https://local.reloop.sh/api/auth |
| Swagger UI | https://local.reloop.sh/api/auth/openapi |
| Stack | ElysiaJS · 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=
| Variable | Required | Default | Notes |
|---|---|---|---|
PG_URL | YES | postgresql://... | Shared Postgres from Docker |
REDIS_URL | YES | redis://... | Sessions and rate limits |
BETTER_AUTH_SECRET | YES | - | Session encryption key |
BASE_URL | YES | https://local.reloop.sh | Public origin for OAuth / invites |
PORT | YES | 8000 | Host port |
NATS_URL | YES | nats://localhost:4222 | Event bus |
DEFAULT_OTP | No | 888888 | Dev OTP bypass |
DISABLE_SIGNUP | No | false | true closes registration on every sign-up path, except pending invitations |
DISABLE_ORG_CREATION | No | false | true stops users creating further organizations |
AUTH_INTERNAL_BASE_URL | No | BASE_URL | Origin 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.
| Who | Result with DISABLE_SIGNUP=true |
|---|---|
| Existing user signing in | Allowed |
| Address with a pending invitation | Allowed |
| Address with an expired invitation | Refused |
| Anyone else, any method | Refused 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=trueonly 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:8000becomeshttp://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
| Command | Description |
|---|---|
bun be:auth:dev | Start dev server with hot reloading |
bun run --filter=be-auth build | Compile production bundle |
bun run --filter=be-auth start | Run compiled production build |
bun run --filter=be-auth check-types | TypeScript type-check |
Architecture
| Layer | Detail |
|---|---|
| Sessions | Better Auth manages login, OAuth, and cookie sessions via Redis + Postgres |
| Multi-tenancy | Users belong to organizations through member (packages/db/src/schema/auth.ts) |
| Events | Auth lifecycle events published to NATS under auth.* |
| First login | Use OTP 888888 locally when DEFAULT_OTP is set |
Next
Was this page helpful?