docs: document Custom JWT, server OIDC role paths, and bootstrap Phase 7b
All checks were successful
CI / build (push) Successful in 1m40s
CI / docker (push) Successful in 19s

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
hsiegeln
2026-04-07 10:55:02 +02:00
parent 67b35a25d6
commit bab9714efc

View File

@@ -58,9 +58,10 @@ Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches
- All API endpoints enforce OAuth2 scopes via `@PreAuthorize("hasAuthority('SCOPE_xxx')")` annotations
- Tenant isolation enforced by `TenantIsolationInterceptor` (a single `HandlerInterceptor` on `/api/**` that resolves JWT org_id to TenantContext and validates `{tenantId}`, `{environmentId}`, `{appId}` path variables; fail-closed, platform admins bypass)
- 13 OAuth2 scopes on the Logto API resource (`https://api.cameleer.local`): 10 platform scopes + 3 server scopes (`server:admin`, `server:operator`, `server:viewer`), served to the frontend from `GET /platform/api/config`
- Server scopes map to server RBAC roles via JWT `scope` claim (server reads `rolesClaim: "scope"`)
- Server scopes map to server RBAC roles via JWT `scope` claim (SaaS platform path) or `roles` claim (server-ui OIDC login path)
- Org role `admin` gets `server:admin`, org role `member` gets `server:viewer`
- Custom `JwtDecoder` in `SecurityConfig.java` — ES384 algorithm, `at+jwt` token type, split issuer-uri (string validation) / jwk-set-uri (Docker-internal fetch), audience validation (`https://api.cameleer.local`)
- Logto Custom JWT (Phase 7b in bootstrap) injects a `roles` claim into access tokens based on org roles and global roles — this makes role data available to the server without Logto-specific code
### Server integration (cameleer3-server env vars)
@@ -72,6 +73,15 @@ Separate Vite+React SPA replacing Logto's default sign-in page. Visually matches
| `CAMELEER_CORS_ALLOWED_ORIGINS` | `${PUBLIC_PROTOCOL}://${PUBLIC_HOST}` | Allow browser requests through Traefik |
| `BASE_PATH` (server-ui) | `/server` | React Router basename + `<base>` tag |
### Server OIDC role extraction (two paths)
| Path | Token type | Role source | How it works |
|------|-----------|-------------|--------------|
| SaaS platform → server API | Logto org-scoped access token | `scope` claim | `JwtAuthenticationFilter.extractRolesFromScopes()` reads `server:admin` from scope |
| Server-ui SSO login | Logto JWT access token (via Traditional Web App) | `roles` claim | `OidcTokenExchanger` decodes access_token, reads `roles` injected by Custom JWT |
The server's OIDC config (`OidcConfig`) includes `audience` (RFC 8707 resource indicator) and `additionalScopes`. The `audience` is sent as `resource` in both the authorization request and token exchange, which makes Logto return a JWT access token instead of opaque. The Custom JWT script maps org roles to `roles: ["server:admin"]`. If OIDC returns no roles and the user already exists, `syncOidcRoles` preserves existing local roles.
### Bootstrap (`docker/logto-bootstrap.sh`)
Idempotent script run via `logto-bootstrap` init container. Phases:
@@ -82,7 +92,8 @@ Idempotent script run via `logto-bootstrap` init container. Phases:
4. Create roles (platform-admin, org admin/member with API resource scope assignments)
5. Create users (SaaS admin with platform-admin role + Logto console access, tenant admin)
6. Create organization, add users with org roles
7. Configure cameleer3-server OIDC (`rolesClaim: "scope"`, `audience`, `defaultRoles: ["VIEWER"]`)
7. Configure cameleer3-server OIDC (`rolesClaim: "roles"`, `audience`, `defaultRoles: ["VIEWER"]`)
7b. Configure Logto Custom JWT for access tokens (maps org roles → `roles` claim: admin→server:admin, member→server:viewer)
8. Configure Logto sign-in branding (Cameleer colors `#C6820E`/`#D4941E`, logo from `/platform/logo.svg`)
9. Cleanup seeded Logto apps
10. Write bootstrap results to `/data/logto-bootstrap.json`