2026-04-05 20:46:00 +02:00
# Single-Domain Routing Design
## Problem
2026-04-06 09:43:14 +02:00
Customers cannot always provision subdomains. The platform must work with a single hostname and one DNS record.
2026-04-05 20:46:00 +02:00
## Solution
2026-04-06 09:43:14 +02:00
Path-based routing on one domain. SaaS app at `/platform` , server-ui at `/server/` , Logto as catch-all. SPA assets moved to `/_app/` to avoid conflict with Logto's `/assets/` .
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
## Routing (all on `${PUBLIC_HOST}`)
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
| Path | Target | Priority | Notes |
|------|--------|----------|-------|
| `/platform/*` | cameleer-saas:8080 | default | Spring context-path `/platform` |
| `/server/*` | cameleer3-server-ui:80 | default | Strip-prefix + `BASE_PATH=/server` |
| `/` | redirect → `/platform/` | 100 | Via `docker/traefik-dynamic.yml` |
| `/*` | logto:3001 | 1 (lowest) | Catch-all: sign-in, OIDC, assets |
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
## Configuration
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
Two env vars control everything:
```env
2026-04-05 20:46:00 +02:00
PUBLIC_HOST=cameleer.mycompany.com
PUBLIC_PROTOCOL=https
2026-04-06 09:43:14 +02:00
```
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
## TLS
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
- **Dev**: `traefik-certs` init container auto-generates self-signed cert on first boot
- **Production**: Traefik ACME (Let's Encrypt)
- HTTP→HTTPS redirect via Traefik entrypoint config
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
## Logto
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
- `ENDPOINT` = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` (same domain as SPA)
- OIDC issuer = `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}/oidc`
- Same origin — no CORS, cookies work
- Management API only via Docker-internal networking (`http://logto:3001` )
- Bootstrap (`docker/logto-bootstrap.sh` ) creates apps, users, orgs, roles, scopes
- Traditional app has `skipConsent: true` for first-party SSO
## SaaS App (cameleer-saas)
- `server.servlet.context-path: /platform` — Spring handles prefix transparently
- Vite `base: '/platform/'` , `assetsDir: '_app'`
- BrowserRouter `basename="/platform"`
- API client: `API_BASE = '/platform/api'`
- Custom `JwtDecoder` : ES384 algorithm, `at+jwt` token type, split issuer-uri / jwk-set-uri
- Redirect URIs: `${PROTO}://${HOST}/platform/callback`
## Server Integration (cameleer3-server)
| Env var | Value | Purpose |
|---------|-------|---------|
| `CAMELEER_OIDC_ISSUER_URI` | `${PROTO}://${HOST}/oidc` | Token issuer claim validation |
| `CAMELEER_OIDC_JWK_SET_URI` | `http://logto:3001/oidc/jwks` | Docker-internal JWK fetch |
| `CAMELEER_OIDC_TLS_SKIP_VERIFY` | `true` | Skip cert verify for OIDC discovery (dev only) |
| `CAMELEER_CORS_ALLOWED_ORIGINS` | `${PROTO}://${HOST}` | Browser requests through Traefik |
Server OIDC requirements:
- ES384 signing algorithm (Logto default)
- `at+jwt` token type acceptance
- `X-Forwarded-Prefix` support for correct redirect_uri construction
- Branding endpoint (`/api/v1/branding/logo` ) must be publicly accessible
## Server UI (cameleer3-server-ui)
| Env var | Value | Purpose |
|---------|-------|---------|
| `BASE_PATH` | `/server` | React Router basename + `<base>` tag |
| `CAMELEER_API_URL` | `http://cameleer3-server:8081` | nginx API proxy target |
Traefik strip-prefix removes `/server` before forwarding to nginx. Server-ui injects `<base href="/server/">` via `BASE_PATH` .
## Bootstrap Redirect URIs
```sh
# SPA (cameleer-saas)
SPA_REDIRECT_URIS=["${PROTO}://${HOST}/platform/callback"]
SPA_POST_LOGOUT_URIS=["${PROTO}://${HOST}/platform/login"]
# Traditional (cameleer3-server) — both variants until X-Forwarded-Prefix is consistent
TRAD_REDIRECT_URIS=["${PROTO}://${HOST}/oidc/callback","${PROTO}://${HOST}/server/oidc/callback"]
TRAD_POST_LOGOUT_URIS=["${PROTO}://${HOST}","${PROTO}://${HOST}/server"]
```
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
## Customer Bootstrap
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
```bash
# 1. Set domain
echo "PUBLIC_HOST=cameleer.mycompany.com" >> .env
echo "PUBLIC_PROTOCOL=https" >> .env
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
# 2. Point DNS (1 record)
# cameleer.mycompany.com → server IP
2026-04-05 20:46:00 +02:00
2026-04-06 09:43:14 +02:00
# 3. Start
docker compose up -d
2026-04-05 20:46:00 +02:00
```
## Future: Custom Sign-In UI (Roadmap)
2026-04-06 09:43:14 +02:00
For full auth UX control, build a custom sign-in experience using Logto's Experience API. Eliminates Logto's interaction pages — Logto becomes a pure OIDC/API backend. Separate project.