Run the frontends¶
The development loop for the two browser applications themselves. To deploy them, see Install it for your organization — this page is about editing them and seeing the change.
IDEN ships two. They are what turns the API into something a person can use, and both are ordinary OIDC clients of the provider with no privileged path of their own.
| App | Dev port | Container port | Path | What it does |
|---|---|---|---|---|
| auth-ui | 4000 | 4000 | /auth/ |
The hosted sign-in page. /oauth2/authorize redirects here for the password step, TOTP, consent, and password recovery. The only place a password is typed. |
| dashboard | 5173 | 3000 | /console/ |
Administration and self-service in one app. What you see is decided by the scopes in your token. |
Both are served from a sub-path, in development as well as in a container, because in a real
deployment all three applications share one origin. The dashboard cannot sit at the root there: the
provider's API owns /admin/* and the dashboard's own admin screens have the same names.
The path is Vite's base in each app's vite.config.ts, and everything else follows from it — the
built asset URLs, React Router's basename, the OIDC redirect URI. Change it in one place and
rebuild; change it anywhere else and the two disagree.
Both are React on Vite in the web/ pnpm workspace, sharing an @iden/shared package: the design
tokens, the generated API types, one axios client, and the components that render identity data.
web/README.md has the layout.
The dev ports differ from the container ports for the dashboard, and both are already accounted for:
5173 and 3000 are in the CORS allowlist and in the seeded dashboard client's redirect URIs, so
either works without configuration.
1. Start the provider¶
The frontends have nothing to show without it. From provider/:
docker compose -f ../deploy/docker-compose.yml up -d postgres redis seaweedfs
uv run python -m scripts.gen_keys # once — the provider will not start without its keys
uv run alembic upgrade head
uv run python -m scripts.seed # prints the bootstrap password, once
uv run provider # http://localhost:8000
Leave seaweedfs out if you are not working on profile photos; those endpoints will answer 503 and
nothing else changes. Run it locally explains these steps in more detail.
2. Start the frontends¶
From web/:
pnpm dev runs both apps in parallel. auth-ui serves on 4000 and the dashboard on 5173, both with
strictPort — if a port is taken, Vite fails rather than quietly moving, because a moved port breaks
the redirect URI.
Open http://localhost:5173/console/ and you are sent through a real sign-in: the dashboard
redirects to /oauth2/authorize, the provider redirects to auth-ui at
http://localhost:4000/auth/login, and you come back to /console/callback with a code.
Note the trailing paths — though you do not have to type them. All three ways of serving these apps
redirect their root to the base: Vite's dev server, each app's own container, and the production
proxy. http://localhost:5173 and http://localhost:3000 both land on /console/, so the dev loop
and the deployment agree about where things live.
Pointing them somewhere else¶
Both read their configuration at runtime, not at build time, so one image serves any deployment. In
pnpm dev that comes from Vite env vars; in a container it comes from /config.js, written by the
entrypoint.
| Variable | Effect |
|---|---|
VITE_IDEN_ISSUER |
The provider's origin — what these apps call. In pnpm dev it is a different origin from the app itself, so the app's own origin (5173 or 4000) must be in IDEN_ALLOWED_ADMIN_ORIGINS: CORS permits the caller, not the callee. Behind one origin in production, nothing needs listing. |
VITE_IDEN_ORG_NAME |
Whose sign-in page this is. Takes the larger type; IDEN drops to a caption beneath it. |
VITE_IDEN_ORG_LOGO |
Optional, sits beside the name. |
Each app has an .env.example next to its package.json — copy it to .env and edit. The container
equivalents drop the VITE_ prefix: IDEN_ISSUER, IDEN_ORG_NAME, IDEN_ORG_LOGO_URL.
Types come from the server¶
Request and response types are generated from the provider's own OpenAPI document, never written by hand:
It reads the schema out of the FastAPI application directly rather than over HTTP, so it works with
no server, database, or Redis running. Re-run it after changing any provider schema, and commit the
result — web/shared/api/schema.d.ts is checked in.
If a type is wrong, the fix is in provider/. Editing the generated file is undone by the next
person who runs the generator.
Before you commit¶
The same three run in CI (.github/workflows/web.yml). Conventions are in
GUIDELINES.md — Frontend Code; the visual system is DESIGN.md, transcribed once into
web/shared/tokens/theme.css — every colour and size resolves there rather than being written
inline.
When something is wrong¶
The dashboard redirects forever and never signs in
The issuer, the CORS allowlist, and the registered redirect URI do not all name the same origin.
All three are compared exactly, and the browser console says which one — a CORS refusal points at
IDEN_ALLOWED_ADMIN_ORIGINS, an invalid_request on the redirect points at the client.
The settings, and how to change them, are in Install it for your organization.
Vite refuses to start: port is already in use
strictPort is deliberate. Something else holds 4000 or 5173 — usually the containerized
frontends from an earlier docker compose up -d. Stop them with
docker compose -f ../deploy/docker-compose.yml stop auth-ui dashboard.
Every API call fails with a CORS error
The origin you are serving from is not in IDEN_ALLOWED_ADMIN_ORIGINS. Credentialed requests
forbid a wildcard, so it has to be named, and the provider has to be restarted after a change.
Sign-in works but the dashboard shows no Administration section
That is the permission system working. The sidebar is built from the scopes in your token — sign
in as someone holding the administrator role.