Reflects current state: path-based routing, SaaS at /platform, Logto catch-all, TLS init container, server integration env vars, custom JwtDecoder for ES384, skip consent for SSO. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
105 lines
3.8 KiB
Markdown
105 lines
3.8 KiB
Markdown
# Single-Domain Routing Design
|
|
|
|
## Problem
|
|
|
|
Customers cannot always provision subdomains. The platform must work with a single hostname and one DNS record.
|
|
|
|
## Solution
|
|
|
|
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/`.
|
|
|
|
## Routing (all on `${PUBLIC_HOST}`)
|
|
|
|
| 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 |
|
|
|
|
## Configuration
|
|
|
|
Two env vars control everything:
|
|
```env
|
|
PUBLIC_HOST=cameleer.mycompany.com
|
|
PUBLIC_PROTOCOL=https
|
|
```
|
|
|
|
## TLS
|
|
|
|
- **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
|
|
|
|
## Logto
|
|
|
|
- `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"]
|
|
```
|
|
|
|
## Customer Bootstrap
|
|
|
|
```bash
|
|
# 1. Set domain
|
|
echo "PUBLIC_HOST=cameleer.mycompany.com" >> .env
|
|
echo "PUBLIC_PROTOCOL=https" >> .env
|
|
|
|
# 2. Point DNS (1 record)
|
|
# cameleer.mycompany.com → server IP
|
|
|
|
# 3. Start
|
|
docker compose up -d
|
|
```
|
|
|
|
## Future: Custom Sign-In UI (Roadmap)
|
|
|
|
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.
|