Rename Java packages from net.siegeln.cameleer3 to net.siegeln.cameleer, update all references in workflows, Docker configs, docs, and bootstrap. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
3.8 KiB
Custom Logto Sign-In UI — IMPLEMENTED
Problem
Logto's default sign-in page uses Logto branding. While we configured colors and logos via PATCH /api/sign-in-exp, control over layout, typography, and components is limited. The sign-in experience is visually inconsistent with the cameleer-server login page.
Goal
Replace Logto's sign-in UI with a custom React SPA that visually matches the cameleer-server login page, using @cameleer/design-system components for consistency across all deployment models.
Scope
MVP (implemented): Username/password sign-in only. Later: Sign-up, forgot password, social login, MFA.
Architecture
Source
Separate Vite+React app at ui/sign-in/. Own package.json, shares @cameleer/design-system v0.1.31.
Build
Custom Logto Docker image (cameleer-logto). ui/sign-in/Dockerfile has a multi-stage build: node stage builds the SPA, then copies dist over Logto's built-in experience directory at /etc/logto/packages/experience/dist/. CI builds and pushes the image.
Note: CUSTOM_UI_PATH env var does NOT work for Logto OSS — it's a Logto Cloud-only feature. The correct approach for self-hosted is replacing the experience dist directory.
Deploy
docker-compose.yml pulls the pre-built cameleer-logto image. No init containers, no shared volumes, no local builds needed.
Auth Flow
User visits /platform/ → LoginPage auto-redirects to Logto OIDC
→ Logto sets interaction cookie, serves custom sign-in UI
→ User enters credentials → Experience API 4-step flow
→ Logto redirects back to /platform/callback with auth code
→ SaaS app exchanges code for token, user lands on dashboard
Experience API
The custom UI communicates with Logto via the Experience API (stateful, cookie-based):
1. PUT /api/experience → 204 (init SignIn)
2. POST /api/experience/verification/password → 200 { verificationId }
3. POST /api/experience/identification → 204 (confirm identity)
4. POST /api/experience/submit → 200 { redirectTo }
The interaction cookie is set by /oidc/auth before the user lands on the sign-in page. All API calls use credentials: "same-origin".
Visual Design
Matches cameleer-server LoginPage exactly:
- Centered
Card(400px max-width, 32px padding) - Logo: favicon.svg + "cameleer" text (24px bold)
- Random witty subtitle (13px muted)
FormField+Inputfor username and password- Amber
Button(primary variant, full-width) Alertfor errors- Background:
var(--bg-base)(light beige) - Dark mode support via design-system tokens
- Favicon bundled in
ui/sign-in/public/favicon.svg(served by Logto, not SaaS)
Files
| File | Purpose |
|---|---|
ui/sign-in/Dockerfile |
Multi-stage: node build + FROM logto:latest + COPY dist |
ui/sign-in/package.json |
React 19 + @cameleer/design-system + Vite 6 |
ui/sign-in/src/SignInPage.tsx |
Login form with Experience API integration |
ui/sign-in/src/SignInPage.module.css |
CSS Modules (matches server LoginPage) |
ui/sign-in/src/experience-api.ts |
Typed Experience API client (4-step flow) |
ui/sign-in/src/main.tsx |
React mount + design-system CSS import |
ui/sign-in/public/favicon.svg |
Cameleer camel logo (bundled in dist) |
ui/src/auth/LoginPage.tsx |
Auto-redirects to Logto OIDC (no button) |
.gitea/workflows/ci.yml |
Builds and pushes cameleer-logto image |
docker-compose.yml |
Uses cameleer-logto image (no build directive) |
Future Extensions
- Add React Router for multiple pages (sign-up, forgot password)
- Fetch
GET /api/.well-known/sign-in-expfor dynamic sign-in method detection - Social login buttons via
POST /api/experience/verification/social - MFA via
POST /api/experience/verification/totp - Consent page via
GET /api/interaction/consent