---
title: Auth
icon: key-new
description: Set up, configure, and run the Reloop auth microservice locally.
---
> For the complete documentation index, see [llms-docs.txt](/llms-docs.txt) or the site index [llms.txt](/llms.txt). Full docs corpus: [llms-full-docs.txt](/llms-full-docs.txt). Prefer markdown URLs (append `.md`) for agent consumption. Product skill: [skill.md](/skill.md).


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

<Tip>
Run [`bun setup`](/docs/setup) once from the monorepo root (Postgres + Redis via Docker). Env files come from `bun setup` / `bun env:setup`.
</Tip>


## 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

```bash
bun be:auth:dev
```

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

<Warning>
Open the dashboard at `https://local.reloop.sh/dashboard`, not `localhost:3001`. Session cookies are scoped to `local.reloop.sh`. Local OTP is `888888`.
</Warning>


## Environment

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

```bash
# 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:

```bash
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:

```bash
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

| 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

<CardGroup cols={2}>
  <Card icon="rocket" title="Setup" href="/setup">
    One-command local bootstrap
  </Card>
  <Card icon="globe" title="Port reference" href="/setup/port">
    Gateway routes and ports
  </Card>
  <Card icon="home" title="Dashboard" href="/setup/frontend/dashboard">
    Product UI that consumes auth
  </Card>
  <Card icon="key-new" title="API Key" href="/setup/backend/api-key">
    Developer API keys
  </Card>
</CardGroup>
