diff --git a/docs/contributing.md b/docs/contributing.md index ec3b2c0..edae38c 100644 --- a/docs/contributing.md +++ b/docs/contributing.md @@ -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 \ diff --git a/docs/core/databases.md b/docs/core/databases.md index 9eea29e..31ac98e 100644 --- a/docs/core/databases.md +++ b/docs/core/databases.md @@ -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" ``` @@ -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 \ diff --git a/docs/core/fga-guide.md b/docs/core/fga-guide.md index 1dfa345..0fd3dea 100644 --- a/docs/core/fga-guide.md +++ b/docs/core/fga-guide.md @@ -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 \ diff --git a/docs/core/mcp.md b/docs/core/mcp.md index 37c2f06..d4ecdc2 100644 --- a/docs/core/mcp.md +++ b/docs/core/mcp.md @@ -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 @@ -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. @@ -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 ` 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. @@ -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" ] } } diff --git a/docs/core/rate-limiting.md b/docs/core/rate-limiting.md index 154993e..16c26ec 100644 --- a/docs/core/rate-limiting.md +++ b/docs/core/rate-limiting.md @@ -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 diff --git a/docs/core/security.md b/docs/core/security.md index d4df522..0d5ead4 100644 --- a/docs/core/security.md +++ b/docs/core/security.md @@ -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. --- diff --git a/docs/core/server-config.md b/docs/core/server-config.md index 645c18a..622a3aa 100644 --- a/docs/core/server-config.md +++ b/docs/core/server-config.md @@ -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: @@ -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. @@ -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). diff --git a/docs/core/sso-guide.md b/docs/core/sso-guide.md index d3da369..b3804f8 100644 --- a/docs/core/sso-guide.md +++ b/docs/core/sso-guide.md @@ -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 ``` diff --git a/docs/deployment/alibaba-cloud.md b/docs/deployment/alibaba-cloud.md index 3e4a7f1..b51e5e6 100644 --- a/docs/deployment/alibaba-cloud.md +++ b/docs/deployment/alibaba-cloud.md @@ -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 \ diff --git a/docs/deployment/binary.md b/docs/deployment/binary.md index 03ec41b..c805648 100644 --- a/docs/deployment/binary.md +++ b/docs/deployment/binary.md @@ -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 \ @@ -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} \ diff --git a/docs/deployment/docker.md b/docs/deployment/docker.md index b8efc4e..177c539 100644 --- a/docs/deployment/docker.md +++ b/docs/deployment/docker.md @@ -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 \ @@ -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 \ @@ -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" @@ -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" @@ -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" \ @@ -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` | diff --git a/docs/deployment/easypanel.md b/docs/deployment/easypanel.md index fcf8955..df5a73d 100644 --- a/docs/deployment/easypanel.md +++ b/docs/deployment/easypanel.md @@ -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 \ diff --git a/docs/deployment/fly-io.md b/docs/deployment/fly-io.md index cb94d0d..a3e22a8 100644 --- a/docs/deployment/fly-io.md +++ b/docs/deployment/fly-io.md @@ -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", diff --git a/docs/deployment/helm-chart.md b/docs/deployment/helm-chart.md index e57448c..7f7350f 100644 --- a/docs/deployment/helm-chart.md +++ b/docs/deployment/helm-chart.md @@ -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)" @@ -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. diff --git a/docs/deployment/heroku.md b/docs/deployment/heroku.md index b7fb48e..92b260a 100644 --- a/docs/deployment/heroku.md +++ b/docs/deployment/heroku.md @@ -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 @@ -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 ``` --- diff --git a/docs/deployment/index.md b/docs/deployment/index.md index e1237ae..223e0e0 100644 --- a/docs/deployment/index.md +++ b/docs/deployment/index.md @@ -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 \ diff --git a/docs/deployment/koyeb.md b/docs/deployment/koyeb.md index ecef37e..836d88d 100644 --- a/docs/deployment/koyeb.md +++ b/docs/deployment/koyeb.md @@ -49,6 +49,7 @@ 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: @@ -56,6 +57,7 @@ Update the start command to pass CLI flags: ./authorizer \ --database-type=$DATABASE_TYPE \ --database-url=$DATABASE_URL \ + --url=$AUTHORIZER_URL \ --jwt-type=$JWT_TYPE \ --jwt-secret=$JWT_SECRET \ --encryption-key=$ENCRYPTION_KEY \ diff --git a/docs/deployment/kubernetes.md b/docs/deployment/kubernetes.md index 15fc3f0..f2372c0 100644 --- a/docs/deployment/kubernetes.md +++ b/docs/deployment/kubernetes.md @@ -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" @@ -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)" diff --git a/docs/deployment/railway.md b/docs/deployment/railway.md index 048f6c1..1a78491 100644 --- a/docs/deployment/railway.md +++ b/docs/deployment/railway.md @@ -37,6 +37,7 @@ After deployment, configure the following required variables in Railway's enviro | `ADMIN_SECRET` | `admin` | | `CLIENT_ID` | `123456` | | `CLIENT_SECRET` | `secret` | +| `AUTHORIZER_URL` | `https://your-app.up.railway.app` *(your public Railway URL)* | ### Optional: metrics bind address and rate limits @@ -58,6 +59,7 @@ Update the start command to pass CLI flags: ./authorizer \ --database-type=$DATABASE_TYPE \ --database-url=$DATABASE_URL \ + --url=$AUTHORIZER_URL \ --jwt-type=$JWT_TYPE \ --jwt-secret=$JWT_SECRET \ --encryption-key=$ENCRYPTION_KEY \ diff --git a/docs/deployment/render.md b/docs/deployment/render.md index 8948cdf..e094afd 100644 --- a/docs/deployment/render.md +++ b/docs/deployment/render.md @@ -41,6 +41,7 @@ Set the following required environment variables: | `ADMIN_SECRET` | `admin` | | `CLIENT_ID` | `123456` | | `CLIENT_SECRET` | `secret` | +| `AUTHORIZER_URL` | `https://your-app.onrender.com` *(your public Render URL)* | ### Optional: metrics bind address and rate limits @@ -62,6 +63,7 @@ Update the start command to pass CLI flags: ./authorizer \ --database-type=$DATABASE_TYPE \ --database-url=$DATABASE_URL \ + --url=$AUTHORIZER_URL \ --jwt-type=$JWT_TYPE \ --jwt-secret=$JWT_SECRET \ --encryption-key=$ENCRYPTION_KEY \ diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index fc9a12d..42f74ee 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -63,6 +63,7 @@ go build -o build/authorizer . ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ @@ -79,6 +80,7 @@ make dev go run main.go \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ @@ -95,6 +97,7 @@ go run main.go \ 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 \ @@ -109,6 +112,7 @@ With PostgreSQL: 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=test \ --encryption-key=test-encryption-key \ @@ -134,6 +138,7 @@ cd authorizer ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ @@ -190,6 +195,7 @@ For a quick local dev setup: --http-port=8080 \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --client-id=123456 \ --client-secret=secret \ --admin-secret=admin \ diff --git a/docs/integrations/gatsbyjs.md b/docs/integrations/gatsbyjs.md index 3539dd9..20e0981 100644 --- a/docs/integrations/gatsbyjs.md +++ b/docs/integrations/gatsbyjs.md @@ -25,6 +25,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/integrations/hasura.md b/docs/integrations/hasura.md index e858920..913ece8 100644 --- a/docs/integrations/hasura.md +++ b/docs/integrations/hasura.md @@ -42,6 +42,7 @@ Configure your Authorizer instance using CLI flags at startup. In v2, all config ./authorizer \ --database-type=postgres \ --database-url="postgres://user:pass@host:5432/authorizer" \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/integrations/react-native.md b/docs/integrations/react-native.md index 195bc4e..fee71ad 100644 --- a/docs/integrations/react-native.md +++ b/docs/integrations/react-native.md @@ -30,6 +30,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/introduction.md b/docs/introduction.md index bfd4cfd..c73372f 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -58,6 +58,7 @@ Authorizer v2 focuses on simpler, more secure configuration and a cleaner operat ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ @@ -72,6 +73,7 @@ Or with Docker: 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 \ diff --git a/docs/migration/v1-to-v2.md b/docs/migration/v1-to-v2.md index c8ee8a4..2edfbc4 100644 --- a/docs/migration/v1-to-v2.md +++ b/docs/migration/v1-to-v2.md @@ -71,6 +71,7 @@ In **v2**: 4. **Ensure required flags are set at startup.** The v2 server will fail to start if critical flags are missing. At minimum you must provide: - `--database-type` and `--database-url` (or individual `--database-host`, `--database-port`, `--database-name`, `--database-username`, `--database-password`) — the server cannot start without a database connection. + - `--url` — this server's own canonical base URL (e.g. `https://auth.example.com`). **Required as of 2.4.0**; the server exits if it is missing or malformed. Not the same as `--allowed-origins` — see [Trusted base URL](../core/security#trusted-base-url). - `--client-id` and `--client-secret` — **required**; the server will exit if they are missing. - `--admin-secret` — needed for admin dashboard access and admin API operations. - `--jwt-type` and `--jwt-secret` (for HMAC algorithms like HS256) or `--jwt-private-key` / `--jwt-public-key` (for RSA/ECDSA algorithms) — needed for token signing and verification. @@ -101,6 +102,7 @@ Pass all config as **CLI arguments** when starting the server: ./authorizer \ --database-type=sqlite \ --database-url=data.db \ + --url=http://localhost:8080 \ --client-id=YOUR_CLIENT_ID \ --client-secret=YOUR_CLIENT_SECRET \ --admin-secret=your-admin-secret \ @@ -115,6 +117,7 @@ For local development (from repo root): make dev # or go run main.go --database-type=sqlite --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 --jwt-secret=test --encryption-key=test-encryption-key --admin-secret=admin \ --client-id=123456 --client-secret=secret ``` @@ -130,6 +133,7 @@ To keep using env vars in your deployment: ./authorizer \ --database-type="$DATABASE_TYPE" \ --database-url="$DATABASE_URL" \ + --url="$AUTHORIZER_URL" \ --client-id="$CLIENT_ID" \ --client-secret="$CLIENT_SECRET" \ --encryption-key="$ENCRYPTION_KEY" \ @@ -151,6 +155,7 @@ docker run -p 8080:8080 \ ./authorizer \ --database-type="$DATABASE_TYPE" \ --database-url="$DATABASE_URL" \ + --url="$AUTHORIZER_URL" \ --client-id="$CLIENT_ID" \ --client-secret="$CLIENT_SECRET" \ --admin-secret="$ADMIN_SECRET" \ @@ -193,6 +198,7 @@ docker run -p 8080:8080 \ ./build/authorizer \ --database-type=sqlite \ --database-url=data.db \ + --url=http://localhost:8080 \ --client-id=YOUR_CLIENT_ID \ --client-secret=YOUR_CLIENT_SECRET \ --admin-secret=your-admin-secret \ @@ -308,7 +314,7 @@ Use these v2 **CLI flags** instead of v1 env or dashboard config. Flag names use | `SMTP_USERNAME`, `SMTP_PASSWORD` | `--smtp-username`, `--smtp-password` | | `SENDER_EMAIL`, `SENDER_NAME` | `--smtp-sender-email`, `--smtp-sender-name` | | `SMTP_LOCAL_NAME` | `--smtp-local-name` | -| Skip TLS verify | `--skip-tls-verification` | +| Skip TLS verify | `--smtp-skip-tls-verification` | ### Twilio (SMS) @@ -353,6 +359,59 @@ To see all flags and defaults: ./authorizer --help ``` +### Upgrading from 2.3.x to 2.4.0 + +Two flags became **mandatory** in 2.4.0. Unlike the changes below, these do +not cause silent regressions — the server refuses to start, so you find out +immediately. Set both before restarting. + +#### `--url` is now required + +Every deployment must declare its own canonical address: + +```bash +./authorizer --url=https://auth.example.com ... +``` + +Previously optional; leaving it empty made the server derive its own host +from request headers, which is a host-header-injection account-takeover +surface (CWE-640) — a forged `Host` causes a genuine password-reset link to +be emailed pointing at an attacker-controlled domain. See +[Trusted base URL](../core/security#trusted-base-url) for the full rationale +and for how `--url` differs from `--allowed-origins`. + +A malformed value is rejected too, not just an empty one: `--url` must be an +absolute `http(s)` URL with a host and no user info. `auth.example.com` +(no scheme) fails startup rather than being silently ignored. + +**If you serve several hostnames from one instance:** that only ever worked +on the vulnerable header-derived path. Pick the canonical one. Verified +organization domains are unaffected — they route email domains to +organizations for home realm discovery and have nothing to do with HTTP +virtual hosting. + +#### `--encryption-key` is required for RS*/ES* deployments + +A deployment using `--jwt-private-key`/`--jwt-public-key` without +`--jwt-secret` refuses to start until `--encryption-key` is set. HMAC +deployments (HS256/384/512) still resolve the key from the JWT secret and are +unaffected, though setting a distinct key is strongly recommended. + +If you ran **2.2.1 through 2.4.0-rc.13** with asymmetric JWTs and no +`--jwt-secret`, treat existing TOTP enrollments as compromised — they were +encrypted under a public constant compiled into the source. Rotate the key +and have affected users re-enroll; ciphertext written under the old key will +not decrypt. Recovery codes are unaffected (unkeyed SHA-256 digests). + +#### Delegated agent checks now fail closed + +If you use [agent identity](../enterprise/agent-identity) and your +authorization model does not declare `type agent`, delegated permission +checks are now **denied** instead of authorizing as the delegating user +alone. Add `type agent` to the model, or set +`--fga-allow-unconstrained-agents` while you migrate. Deployments not using +delegation are unaffected. + ### Breaking changes — April 2026 security batch If you are upgrading **across** the April 2026 security release (any @@ -553,6 +612,7 @@ Run with args: docker run -p 8080:8080 your-image \ --database-type=postgres \ --database-url="postgres://user:pass@host/db" \ + --url=http://localhost:8080 \ --client-id=... \ --client-secret=... \ --admin-secret=... \ diff --git a/docs/sdks/authorizer-js/index.md b/docs/sdks/authorizer-js/index.md index 54fdc97..34fbe8c 100644 --- a/docs/sdks/authorizer-js/index.md +++ b/docs/sdks/authorizer-js/index.md @@ -34,6 +34,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/sdks/authorizer-python/index.md b/docs/sdks/authorizer-python/index.md index 28408bc..72b8fc5 100644 --- a/docs/sdks/authorizer-python/index.md +++ b/docs/sdks/authorizer-python/index.md @@ -36,6 +36,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/sdks/authorizer-react/index.md b/docs/sdks/authorizer-react/index.md index 546bc62..318bae2 100644 --- a/docs/sdks/authorizer-react/index.md +++ b/docs/sdks/authorizer-react/index.md @@ -29,6 +29,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/sdks/authorizer-svelte/index.md b/docs/sdks/authorizer-svelte/index.md index 8f3ac89..b2a1c47 100644 --- a/docs/sdks/authorizer-svelte/index.md +++ b/docs/sdks/authorizer-svelte/index.md @@ -29,6 +29,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \ diff --git a/docs/sdks/authorizer-vue/index.md b/docs/sdks/authorizer-vue/index.md index 43f8501..89b22fd 100644 --- a/docs/sdks/authorizer-vue/index.md +++ b/docs/sdks/authorizer-vue/index.md @@ -29,6 +29,7 @@ Start your Authorizer instance with the required CLI flags: ./authorizer \ --database-type=sqlite \ --database-url=test.db \ + --url=http://localhost:8080 \ --jwt-type=HS256 \ --jwt-secret=test \ --encryption-key=test-encryption-key \