Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions docs/contributing.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,6 +52,7 @@ Please ask as many questions as you need, either directly in the issue or on [Di
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
3 changes: 2 additions & 1 deletion docs/core/databases.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -115,7 +115,7 @@ Authorizer v2 configures the database via CLI flags. The required flags are `--d
--database-username="admin" \
--database-password="*********" \
--couchbase-bucket="authorizer" \
--couchbase-bucket-ram-quota=1000 \
--couchbase-ram-quota=1000 \
--couchbase-scope="_default"
```

Expand DownExpand Up@@ -144,6 +144,7 @@ Example with Redis:
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
1 change: 1 addition & 0 deletions docs/core/fga-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -27,6 +27,7 @@ by default — this is all you need to follow along:
docker run -p 8080:8080 quay.io/authorizer/authorizer:latest \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
23 changes: 18 additions & 5 deletions docs/core/mcp.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -272,9 +272,9 @@ authorizer mcp \
--client-id=YOUR_CLIENT_ID \
--database-type=sqlite \
--database-url=auth.db \
--url=http://localhost:8080 \
--encryption-key=your-encryption-key \
--mcp-bearer="$USER_ACCESS_TOKEN" \
--mcp-authorizer-url=https://auth.example.com
--mcp-bearer="$USER_ACCESS_TOKEN"
```

With a SQLite/Postgres/MySQL `--database-type`, FGA reuses the main database
Expand All@@ -284,6 +284,19 @@ automatically — no `--fga-store` flag needed (see
you want FGA on a separate store; `--fga-store` takes one of `sqlite`,
`postgres`, `mysql`, or `memory` — not a URI.

:::warning `--mcp-authorizer-url` has no effect as of 2.4.0
Pass `--url` with the same value instead — it is inherited from the root flag
set and is what the token's `iss` claim is validated against.

The old flag is still accepted so existing setups keep starting, but it is
**ignored**: it only ever stamped an `x-authorizer-url` header, and `--url`
is consulted before any header. It warns when used and goes away in 2.5.0
with the subcommand.

`--mcp-bearer` without `--url` is now refused at startup, rather than failing
later as `Unauthenticated` on every tool call.
:::

The `mcp` command inherits the root server flags (database, JWT, client-id, `--fga-store`,
etc.) so it can resolve identity and run the FGA engine in-process.

Expand All@@ -292,7 +305,7 @@ etc.) so it can resolve identity and run the FGA engine in-process.
| Flag | Description | Required |
| ----------------------- | ---------------------------------------------------------------------------------------------------------------- | --------------- |
| `--mcp-bearer` | Access token attached as `Authorization: Bearer <token>` on every tool call. Needed for `profile`/`*_permissions`. | for auth tools |
| `--mcp-authorizer-url` | Public URL of your Authorizer instance, used for JWT issuer validation (e.g. `https://auth.example.com`). | with `--mcp-bearer` |
| `--url` | This server's own base URL — the value the token's `iss` claim is validated against. Inherited from the root flag set. | with `--mcp-bearer` |

> Logging goes to **stderr** only — `stdout` is reserved for the MCP JSON-RPC stream, so
> never print to it.
Expand All@@ -314,8 +327,8 @@ Most MCP hosts read a JSON config that declares the command to spawn. For
"--database-type", "sqlite",
"--database-url", "auth.db",
"--encryption-key", "your-encryption-key",
"--mcp-bearer", "USER_ACCESS_TOKEN",
"--mcp-authorizer-url", "https://auth.example.com"
"--url", "https://auth.example.com",
"--mcp-bearer", "USER_ACCESS_TOKEN"
]
}
}
Expand Down
1 change: 1 addition & 0 deletions docs/core/rate-limiting.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -142,6 +142,7 @@ services:
command:
- --database-type=postgres
- --database-url=postgres://user:pass@db:5432/authorizer
- --url=https://auth.example.com
- --encryption-key=your-encryption-key
- --redis-url=redis://redis:6379
- --rate-limit-rps=30
Expand Down
58 changes: 47 additions & 11 deletions docs/core/security.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -127,22 +127,58 @@ proxy you **must** set this flag, otherwise:
./authorizer --url=https://auth.example.com
```

- **`--url`** (default empty): the operator-configured canonical base URL of
this Authorizer instance. When set, it is the **only** source used to
:::warning Required as of 2.4.0
The server **refuses to start** without `--url`. Deployments upgrading from
2.3.x that never set it will fail to boot until they do — see
[Upgrading to 2.4.0](../migration/v1-to-v2#upgrading-from-23x-to-240).
:::

- **`--url`** (**required**): the operator-configured canonical base URL of
this Authorizer instance. It is the **only** source used to
derive the server's own host — verification/reset/magic-link email links,
the JWT `iss` claim, and the OIDC discovery/JWKS document URLs — and every
request header that could otherwise influence it (`X-Authorizer-URL`,
`X-Forwarded-Host`, `Host`) is ignored. The value is normalized to
scheme+host (path, query, fragment, userinfo, and trailing slash stripped)
and pinned once at startup, before any listener accepts a connection.

When **empty** (the default), Authorizer falls back to header-based
derivation (`X-Authorizer-URL`, then `X-Forwarded-Host`/`Host`), which
preserves flexible reverse-proxy / multi-tenant setups but leaves a
host-header-injection account-takeover surface (CWE-640): a request with a
forged host header can cause a password-reset or verification email to
contain a link pointing at an attacker-controlled domain. **Set `--url` in
production**, particularly behind a reverse proxy, to close this off.
and pinned once at startup, before any listener accepts a connection. A
value that cannot be normalized — `auth.example.com` with no scheme, or a
URL carrying user info — is **rejected at startup** rather than silently
ignored, since either would otherwise start in the vulnerable configuration
while looking configured.

Before 2.4.0 the flag was optional, and leaving it empty fell back to
header-based derivation (`X-Authorizer-URL`, then `X-Forwarded-Host`/`Host`).
That fallback is a host-header-injection account-takeover surface (CWE-640):
a request with a forged host header causes a password-reset or verification
email to carry a **genuine** single-use link pointing at an attacker-controlled
domain — no prior access and no mailbox compromise required. Making the flag
mandatory is the only fix that closes the class; validating the derived host
against `--allowed-origins` would help only deployments that configured an
explicit list, and would do nothing on the default `*` — which is the
configuration the attack targets.

This costs no supported capability. Setting `--url` already collapsed an
instance to a single canonical host, so serving several hostnames from one
instance only ever worked on the vulnerable path. Verified organization
domains are email-domain-to-organization routing for home realm discovery,
not HTTP virtual hosting, and are unaffected.

### `--url` vs `--allowed-origins` vs the SDKs' `authorizerURL`

Three similarly-named settings that mean different things. Confusing `--url`
with `--allowed-origins` is the most common misconfiguration:

| Setting | Lives in | Names | Example |
|---|---|---|---|
| `--url` | server flag | **This server's own address** — the single host Authorizer believes it is reachable at. | `https://auth.example.com` |
| `--allowed-origins` | server flag | **The other apps** permitted to call this server cross-origin and be redirected to. | `https://app.example.com,https://admin.example.com` |
| `authorizerURL` | SDK / client option | **The client's pointer back at the server** — what a browser or backend SDK dials. Normally the same value as `--url`. | `https://auth.example.com` |

You need both flags and they are not interchangeable: `--url` is *where
Authorizer is*, `--allowed-origins` is *who may talk to it*. There is no
`--authorizer-url` server flag; `authorizerURL` is client-side only, and
`X-Authorizer-URL` is the legacy request header `--url` replaces — ignored
outright as of 2.4.0.

---

Expand Down
23 changes: 11 additions & 12 deletions docs/core/server-config.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -92,13 +92,16 @@ These flags replace v1 env such as `CLIENT_ID`, `CLIENT_SECRET`, and app behavio
April 2026**: defaults to none — operators behind a proxy must set this
explicitly or rate limiting and audit logs will key on the proxy IP. See
[Trusted proxies](./security#trusted-proxies).
- **`--url`** (default empty): canonical/trusted base URL of this instance
(e.g. `https://auth.example.com`). When set, it is the **only** source used
to build verification/reset/magic-link email URLs, the JWT `iss` claim, and
OIDC discovery URLs — the `X-Authorizer-URL`, `X-Forwarded-Host`, and `Host`
request headers are ignored for that purpose. Leaving it empty keeps legacy
header-based derivation. **Recommended for production**, especially behind
a reverse proxy. See [Trusted base URL](./security#trusted-base-url).
- **`--url`** (**required** as of 2.4.0): this server's own canonical base URL
(e.g. `https://auth.example.com`). It is the only source used to build
verification/reset/magic-link email URLs, the JWT `iss` claim, and OIDC
discovery URLs; the `X-Authorizer-URL`, `X-Forwarded-Host` and `Host`
request headers are ignored for that purpose. The server refuses to start
if it is missing or malformed. **This is not `--allowed-origins`** — that
lists the *apps* allowed to talk to this server, while `--url` is *where
this server is*; you need both. See
[Trusted base URL](./security#trusted-base-url) for the rationale and a
comparison with the SDKs' `authorizerURL` option.

Organization / UI:

Expand DownExpand Up@@ -436,9 +439,7 @@ metric, labelled by limit kind. See
```bash
./authorizer \
--fga-store=postgres \
--fga-store-url="postgres://user:pass@host/db" \
--include-permissions-in-token=false \
--authorization-log-all-checks=false
--fga-store-url="postgres://user:pass@host/db"
```

- **`--fga-store`**: backing store for the embedded [OpenFGA](https://openfga.dev) engine — one of `sqlite`, `postgres`, `mysql`, or `memory`. Only needed when the main database is NoSQL (see paragraph below); for SQL main databases the engine reuses that database automatically.
Expand DownExpand Up@@ -470,8 +471,6 @@ The fix is to add `type agent` to your model — see
[Agent identity](../enterprise/agent-identity). Grant your agents *before*
deploying the model, or their calls start being denied.
:::
- **`--include-permissions-in-token`** (default `false`): when true, the access token's claims include the caller's flat `(resource, scope)` grant list. Useful for stateless downstream services that don't want to round-trip back to Authorizer per check.
- **`--authorization-log-all-checks`** (default `false`): audit-log every `CheckPermission` call, not just denials. Diagnostic; expensive at scale.

Authorizer ships an embedded **OpenFGA** (ReBAC) engine. It is enabled by default when the main database is SQL-compatible (SQLite/Postgres/MySQL) and reuses that database. For NoSQL main databases (MongoDB, DynamoDB, …) it is off unless you set `--fga-store` (one of `sqlite`/`postgres`/`mysql`/`memory`) and `--fga-store-url`. Checks fail closed. See [Authorization (FGA)](./authorization).

Expand Down
7 changes: 4 additions & 3 deletions docs/core/sso-guide.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -52,17 +52,18 @@ Deploy a single Authorizer instance as your organization's IdP. See the [Deploym
Configure the essentials:

```bash
authorizer serve \
authorizer \
--database-type postgres \
--database-url "postgres://user:pass@db:5432/authorizer" \
--port 8080 \
--url https://auth.yourcompany.com \
--http-port 8080 \
--allowed-origins "https://app1.yourcompany.com,https://app2.yourcompany.com,https://admin.yourcompany.com" \
--organization-name "YourCompany" \
--smtp-host "smtp.yourcompany.com" \
--smtp-port 587 \
--smtp-username "auth@yourcompany.com" \
--smtp-password "..." \
--sender-email "auth@yourcompany.com" \
--smtp-sender-email "auth@yourcompany.com" \
--encryption-key your-encryption-key
```

Expand Down
1 change: 1 addition & 0 deletions docs/deployment/alibaba-cloud.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -29,6 +29,7 @@ After deployment, configure the required v2 variables in your instance:
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=https://auth.example.com \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
2 changes: 2 additions & 0 deletions docs/deployment/binary.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -50,6 +50,7 @@ Example for SQLite:
--http-port=8080 \
--database-type=sqlite \
--database-url=data.db \
--url=https://auth.example.com \
--client-id=YOUR_CLIENT_ID \
--client-secret=YOUR_CLIENT_SECRET \
--admin-secret=your-admin-secret \
Expand DownExpand Up@@ -83,6 +84,7 @@ ExecStart=/path_to_authorizer_parent_folder/authorizer/build/authorizer \
--http-port=8080 \
--database-type=postgres \
--database-url=${DATABASE_URL} \
--url=https://auth.example.com \
--client-id=${CLIENT_ID} \
--client-secret=${CLIENT_SECRET} \
--admin-secret=${ADMIN_SECRET} \
Expand Down
6 changes: 6 additions & 0 deletions docs/deployment/docker.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -15,6 +15,7 @@ Run Authorizer using Docker with the v2 **CLI-only configuration model**.
docker run -p 8080:8080 quay.io/authorizer/authorizer:latest \
--database-type=sqlite \
--database-url=test.db \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand DownExpand Up@@ -54,6 +55,7 @@ The image **`EXPOSE`s `8080`, `8081`, and `9091`**. That only **documents** whic
docker run -p 8080:8080 quay.io/authorizer/authorizer:latest \
--database-type=postgres \
--database-url="postgres://user:pass@host:5432/authorizer" \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret=your-jwt-secret \
--encryption-key=test-encryption-key \
Expand All@@ -78,6 +80,7 @@ services:
command:
- "--database-type=sqlite"
- "--database-url=/data/test.db"
- "--url=http://localhost:8080"
- "--jwt-type=HS256"
- "--jwt-secret=test"
- "--encryption-key=test-encryption-key"
Expand DownExpand Up@@ -128,6 +131,7 @@ services:
command:
- "--database-type=postgres"
- "--database-url=postgres://authorizer:secret@postgres:5432/authorizer"
- "--url=http://localhost:8080"
- "--redis-url=redis://redis:6379"
- "--jwt-type=HS256"
- "--jwt-secret=test"
Expand DownExpand Up@@ -159,6 +163,7 @@ docker run -p 8080:8080 \
quay.io/authorizer/authorizer:latest \
--database-type="$DATABASE_TYPE" \
--database-url="$DATABASE_URL" \
--url=http://localhost:8080 \
--jwt-type=HS256 \
--jwt-secret="$JWT_SECRET" \
--encryption-key="$ENCRYPTION_KEY" \
Expand All@@ -175,6 +180,7 @@ docker run -p 8080:8080 \
| ---- | ----------- | ------- |
| `--database-type` | Database type | `sqlite`, `postgres`, `mysql` |
| `--database-url` | Database connection string | `test.db` |
| `--url` | This server's own public base URL **(required)** | `https://auth.example.com` |
| `--jwt-type` | JWT signing algorithm | `HS256`, `RS256` |
| `--jwt-secret` | JWT signing secret (for HS256) | `test` |
| `--admin-secret` | Admin secret for admin operations | `admin` |
Expand Down
1 change: 1 addition & 0 deletions docs/deployment/easypanel.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,6 +25,7 @@ After deployment, update the start command to include the required v2 CLI flags:
./authorizer \
--database-type=sqlite \
--database-url=test.db \
--url=https://your-app.easypanel.host \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
1 change: 1 addition & 0 deletions docs/deployment/fly-io.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -70,6 +70,7 @@ cmd = [
"./authorizer",
"--database-type=postgres",
"--database-url=postgres://user:pass@localhost:5432/authorizer",
"--url=https://authorizer.fly.dev",
"--jwt-type=HS256",
"--jwt-secret=test",
"--encryption-key=$ENCRYPTION_KEY",
Expand Down
6 changes: 6 additions & 0 deletions docs/deployment/helm-chart.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -155,6 +155,7 @@ containers:
args:
- "--database-type=$(DATABASE_TYPE)"
- "--database-url=$(DATABASE_URL)"
- "--url=$(AUTHORIZER_URL)"
- "--jwt-type=HS256"
- "--jwt-secret=$(JWT_SECRET)"
- "--encryption-key=$(ENCRYPTION_KEY)"
Expand All@@ -172,6 +173,11 @@ containers:
secretKeyRef:
name: authorizer-secrets
key: database-url
- name: AUTHORIZER_URL
valueFrom:
secretKeyRef:
name: authorizer-secrets
key: authorizer-url
```

Until the next Helm chart version is released, you can use the existing chart with the current values or deploy using raw Kubernetes manifests as shown in the [Kubernetes](./kubernetes) guide.
3 changes: 2 additions & 1 deletion docs/deployment/heroku.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ For Authorizer v2, configure the following required variables in your Heroku app
| `ADMIN_SECRET` | `admin` |
| `CLIENT_ID` | `123456` |
| `CLIENT_SECRET` | `secret` |
| `AUTHORIZER_URL` | `https://your-app.herokuapp.com` *(your public Heroku URL)* |

### Optional: metrics bind address and rate limits

Expand All@@ -53,7 +54,7 @@ Use `REDIS_URL` for shared sessions and rate limits across dynos ([rate limiting
Update the Procfile or startup command to pass CLI flags:

```
web: ./authorizer --database-type=$DATABASE_TYPE --database-url=$DATABASE_URL --jwt-type=$JWT_TYPE --jwt-secret=$JWT_SECRET --encryption-key=$ENCRYPTION_KEY --admin-secret=$ADMIN_SECRET --client-id=$CLIENT_ID --client-secret=$CLIENT_SECRET
web: ./authorizer --database-type=$DATABASE_TYPE --database-url=$DATABASE_URL --url=$AUTHORIZER_URL --jwt-type=$JWT_TYPE --jwt-secret=$JWT_SECRET --encryption-key=$ENCRYPTION_KEY --admin-secret=$ADMIN_SECRET --client-id=$CLIENT_ID --client-secret=$CLIENT_SECRET
```

---
Expand Down
1 change: 1 addition & 0 deletions docs/deployment/index.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -35,6 +35,7 @@ All deployments require these flags with sample values:
```bash
--database-type=sqlite \
--database-url=test.db \
--url=https://auth.example.com \
--jwt-type=HS256 \
--jwt-secret=test \
--encryption-key=test-encryption-key \
Expand Down
2 changes: 2 additions & 0 deletions docs/deployment/koyeb.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -49,13 +49,15 @@ Add the following environment variables:
| `ADMIN_SECRET` | `admin` |
| `CLIENT_ID` | `123456` |
| `CLIENT_SECRET` | `secret` |
| `AUTHORIZER_URL` | `https://your-app.koyeb.app` *(your public Koyeb URL)* |

Update the start command to pass CLI flags:

```bash
./authorizer \
--database-type=$DATABASE_TYPE \
--database-url=$DATABASE_URL \
--url=$AUTHORIZER_URL \
--jwt-type=$JWT_TYPE \
--jwt-secret=$JWT_SECRET \
--encryption-key=$ENCRYPTION_KEY \
Expand Down
6 changes: 6 additions & 0 deletions docs/deployment/kubernetes.md
Original file line numberDiff line numberDiff line change
Expand Up@@ -79,6 +79,11 @@ spec:
secretKeyRef:
name: authorizer-secrets
key: admin-secret
- name: ENCRYPTION_KEY
valueFrom:
secretKeyRef:
name: authorizer-secrets
key: encryption-key
args:
- "--env=production"
- "--http-port=8080"
Expand All@@ -89,6 +94,7 @@ spec:
- "--rate-limit-fail-closed=false"
- "--database-type=postgres"
- "--database-url=$(DATABASE_URL)"
- "--url=https://YOUR_DOMAIN"
- "--client-id=$(CLIENT_ID)"
- "--client-secret=$(CLIENT_SECRET)"
- "--admin-secret=$(ADMIN_SECRET)"
Expand Down
Loading