Files
zitadel/apps/docs/content/guides/integrate/login-ui/login-app.mdx
T
7e34f04361 docs: fix broken links to renamed repository paths (#12618)
# Which Problems Are Solved

Several links in the docs point at repository paths that no longer
exist, so they 404 on GitHub:

- The `load-test` directory was renamed to `benchmark`. The benchmarks
page still links to `load-test`, `load-test/Makefile` and
`load-test/README.md` (5 occurrences).
- `apps/docs/content/index.mdx` links to `CONTRIBUTING.mdx`, but the
file is `CONTRIBUTING.md`.
- The login-app guide links to `apps/login/src/middleware.ts`, which was
renamed to `apps/login/src/proxy.ts` in the Next.js 16 upgrade (#11629).

# How the Problems Are Solved

- Point the benchmarks page at `benchmark/`, `benchmark/Makefile` and
`benchmark/README.md`. The `#test` anchor still resolves —
`benchmark/README.md` has a `### Test` heading.
- Fix the `CONTRIBUTING.md` extension.
- Point the middleware example at `apps/login/src/proxy.ts`.

Verified by extracting every
`github.com/zitadel/zitadel/{blob,tree}/main/...` link from
`apps/docs/content` and resolving each one against the working tree —
all 14 distinct paths now exist.

# Additional Changes

- Prose in the benchmarks page that names the directory ("the load-test
Makefile", "the load-test readme") now says "benchmark". Generic uses of
"load-test"/"load-testing" as an activity are left as they are.

# Additional Context

- The versioned docs under `apps/docs/content/v4.13`–`v4.16` contain the
same stale links, but those directories are generated at build time from
release tags by `apps/docs/scripts/fetch-remote-content.mjs` and are
gitignored, so they cannot be fixed from `main`. The published pages for
those versions will keep the old links.
- `apps/docs/scripts/check-links.ts` skips anything starting with
`http`, which is why these went unnoticed. Teaching it to resolve
`github.com/zitadel/zitadel/blob/main/<path>` links against the working
tree would catch this class of breakage in CI — not done here to keep
the PR focused.

Co-authored-by: Claude Opus 5 <[email protected]>
2026-09-09 16:06:20 +00:00

152 lines
8.1 KiB
Plaintext

---
title: Login App
description: "Building a custom login application with ZITADEL: an overview of the OIDC and Session API integration."
sidebar_label: Login App
---
To replace the old embedded Login built with Golang and to showcase the use of our session and OIDC APIs, we've created the new [Login app](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#contribute-to-login).
## Used Apps And Packages
- **Login app**: The Login app used by ZITADEL Cloud, powered by Next.js
- `@zitadel/proto`: Typescript implementation of Protocol Buffers, suitable for web browsers and Node.js.
- `@zitadel/client`: Core components for establishing a client connection
## Login App
The new implementation of the Login app is based on Next.js and implements the [OIDC standard](./oidc-standard) by proxying requests to the ZITADEL backend and using the session API.
The UI uses the branding and behaves based on the policies and settings of your instance or organization.
It is also allows users to authenticate directly (without an OIDC context) and self service their account.
Its set of features will increase with time and its progress will be tracked [here](./login-app#implemented-features).
### Architectural Overview
The new Login app consists of a confidential (backend) part and a browser (client side) part.
In addition to that, the OIDC specification builds upon a proxy which is set up in the Next.js middleware. It rewrites requests of the paths `/.well-known/:path*`, `/oauth/:path*`, `/oidc/:path*` to ZITADEL.
The backend side of the application is authorized using a service account's personal access token.
This token is used to fetch policies and settings from your instances or organization which define how users are authenticated and how the Login app is displayed.
The frontend side of the application communicates to the backend side by using a cookie which is set by the server.
The cookie consists of an id and a token and is bound to a session which is updated continuously.
The following illustration shows the architecture of the Login app and a potential authentication code flow starting from an application which implements the OIDC specification respectively.
![](../../../../public/img/typescript-login-architecture.png)
_Note that the illustration is just a representation of the architecture and may not specify actual endpoints._
The flow starts by the `/authorize` endpoint which is intercepted by the Login app and forwarded to ZITADEL.
ZITADEL interprets the request and specifically redirects back to the Login app.
It does so by redirecting to `/login`.
The Login app is then able to load an [AuthRequest](/reference/api/oidc/zitadel.oidc.v2.OIDCService.GetAuthRequest).
The Auth Request defines how users proceed to authenticate. If no special prompts or scopes are set, the Login app brings up the `/loginname` page.
The /loginname page allows to enter loginname or email of a user which is then used to search for a user.
If the user is found, a session is created and set as cookie, then the user is redirected to the available authentication method page.
While the users continues and provides more information, the cookie is hydrated with this information until a final state is reached.
The communication from the browser to the server is done by [NextJS Server Actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations).
The OIDC Auth request is always passed in the url to have a context to the ongoing authentication flow.
If enough user information is retrieved and the user is authenticated according to the policies, the flow is finalized by [requesting a the callback url](/reference/api/oidc/zitadel.oidc.v2.OIDCService.CreateCallback) for the auth request and the user is redirected back to the application.
The application can then request a token calling the /token endpoint of the Login app which is proxied to the ZITADEL API.
### Implemented features
#### OIDC Standard
- [x] Authorization Code Flow with PKCE
- [x] AuthRequest `hintUserId`
- [x] AuthRequest `loginHint`
- [x] AuthRequest `prompt`
- [x] Login
- [x] Select Account
- [x] Create
- [x] None
- [ ] Consent
- Scopes
- [x] `openid email profile address`
- [x] `offline access`
- [x] `urn:zitadel:iam:org:idp:id:{idp_id}`
- [x] `urn:zitadel:iam:org:project:id:zitadel:aud`
- [x] `urn:zitadel:iam:org:id:{orgid}`
- [x] `urn:zitadel:iam:org:domain:primary:{domain}`
- [ ] AuthRequest UI locales
- Multifactor
- [x] Passkeys
- [x] TOTP
- [x] OTP via email
- [x] OTP via SMS
- Passwordless
- [x] Passkeys
- Security Prompts
- [x] Setup Passkey as Passwordless method
- [x] Setup TOTP as Multifactor
- [ ] Password Change
- Login
- [x] Email Password
- [x] Passkey
- [x] IDPs
- [x] Google
- [x] GitHub
- [x] GitLab
- [x] Azure
- [ ] Apple
- Register
- [x] Email Password
- [x] Passkey
#### Self service
The self service part of the application allows to load the Login app without an OIDC flow.
Authenticated users are directly able to self service their account.
- [ ] Change user information
- [ ] Password Change
- [x] Setup Passkey
- [x] Setup Multifactor
- [x] Passkeys
- [x] TOTP
- [x] OTP via email
- [x] OTP via SMS
- [x] Setup Multifactor Passkey (U2F)
- [x] Validate Account (email verification)
### Setup
In order to run the new Login app, make sure to follow the instructions in the [contribution guide](https://github.com/zitadel/zitadel/blob/main/CONTRIBUTING.md#contribute-to-login).
#### How to setup domains for OIDC
When setting up the new Login app for OIDC, the domain must be registered on your instance and use https.
To register your login domain on your instance, [add](/apis/resources/admin/admin-service-add-instance-trusted-domain) your domain on your instances Trusted Domains.
#### OIDC Proxy
When setting up the new Login app for OIDC, ensure it meets the following requirements:
- The OIDC Proxy is deployed and running on HTTPS
- The OIDC Proxy sets `x-zitadel-public-host` which is the host, your Login app is deployed to `ex. login.example.com`.
- The OIDC Proxy sets `x-zitadel-instance-host` which is the host of your instance `ex. test-hdujwl.zitadel.cloud`.
You can review an example implementation of a middleware [here](https://github.com/zitadel/zitadel/blob/main/apps/login/src/proxy.ts).
#### Deploy to Vercel
<a href="https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.com%2Fzitadel%2Fzitadel&env=ZITADEL_API_URL,ZITADEL_SERVICE_USER_ID,ZITADEL_SERVICE_USER_TOKEN&root-directory=apps/login&envDescription=Setup%20a%20service%20account%20with%20IAM_LOGIN_CLIENT%20membership%20on%20your%20instance%20and%20provide%20its%20id%20and%20personal%20access%20token.&project-name=zitadel-login&repository-name=zitadel-login" target="_blank" rel="noopener noreferrer" style={{cursor: "pointer", display: "inline-block", padding: "2px"}}>
<img src="https://vercel.com/button" alt="Deploy with Vercel" style={{pointerEvents: "none", display: "block"}}/>
</a>
To deploy your own version on Vercel, navigate to your instance and create a service account.
Create a personal access token (PAT) for the user and copy and set it as `ZITADEL_SERVICE_USER_TOKEN`, then navigate to Default settings and make sure it gets `IAM_LOGIN_CLIENT` permissions.
Finally set your instance url as `ZITADEL_API_URL`. Make sure to set it without trailing slash.
Also ensure your login domain is registered on your instance by adding it as a [Trusted Domain](/apis/resources/admin/admin-service-add-instance-trusted-domain).
If you want to enforce users to have their email verified, you can set the optional `EMAIL_VERIFICATION` variable to `true` in your environment and your users will be enforced to verify their email address before they can log in.
By default, verification codes are not automatically submitted on page load. This protects against enterprise email link scanners that pre-fetch URLs and could inadvertently consume one-time codes before users click them.
If you want to enable automatic code submission (e.g. for a smoother UX when link scanners are not a concern), set `NEXT_PUBLIC_AUTO_SUBMIT_CODE` to `true`.
![](../../../../public/img/deploy-to-vercel.png)