Skip to content

feat(auth): custom Ory Elements login UI#380

Merged
huv1k merged 14 commits into
mainfrom
huv1k/ory-custom-login-flows
Jun 11, 2026
Merged

feat(auth): custom Ory Elements login UI#380
huv1k merged 14 commits into
mainfrom
huv1k/ory-custom-login-flows

Conversation

@huv1k

@huv1k huv1k commented Jun 10, 2026

Copy link
Copy Markdown
Member

Replace the stock Kratos self-service UI for the login flow with a custom @ory/elements-react page at /login, styled with the dashboard's own design system (no Ory default theme CSS).

Gating (opt-in flag)

The custom Elements UI is gated behind isOryCustomUiEnabled() in src/configs/flags.ts:

isOryCustomUiEnabled() = isOryAuthEnabled() && process.env.NEXT_PUBLIC_ORY_CUSTOM_UI === '1'

Ory auth itself is unchanged in production — only the new custom UI is gated off there. The gate lives in two places:

  • src/proxy.ts — the Ory-SDK same-origin short-circuit only runs when the flag is on (production proxy behaves exactly as before).
  • /login and /registration pages — when the flag is off, they redirect() to the existing /sign-in / /sign-up flow instead of rendering the custom card.

The custom UI activates wherever AUTH_PROVIDER=ory and NEXT_PUBLIC_ORY_CUSTOM_UI=1 (set in staging/preview and by the local dev harness); production leaves the flag unset and keeps Ory's existing login.

Why an explicit NEXT_PUBLIC_ flag instead of VERCEL_ENV: isOryCustomUiEnabled() runs in Edge middleware, where non-public env vars like VERCEL_ENV/AUTH_PROVIDER are inlined at build time and resolve to undefined in standalone/self-hosted builds (anything but next dev) — which would silently disable the same-origin proxy. A NEXT_PUBLIC_ flag is read at request time in middleware, server, and client alike.

Login

Before After
CleanShot 2026-06-10 at 16 18 29@2x CleanShot 2026-06-10 at 17 29 33@2x

Register

Before After
CleanShot 2026-06-10 at 16 15 04@2x CleanShot 2026-06-10 at 16 15 29@2x
  • add @ory/elements-react + @ory/nextjs deps
  • src/configs/ory.ts: OryClientConfiguration (login-only scope)
  • src/app/login: server page (getLoginFlow + per-request sdk.url = own origin), client wrapper, layout, and component overrides for card/header/ input/button/label/sso/form-group/message
  • src/proxy.ts: wire @ory/nextjs createOryMiddleware so /self-service etc. proxy to Kratos same-origin (first-party cookies, no CORS)
  • gate custom UI on NEXT_PUBLIC_ORY_CUSTOM_UI (was VERCEL_ENV)
  • env: NEXT_PUBLIC_ORY_SDK_URL (browser-facing Kratos public URL); NEXT_PUBLIC_ORY_CUSTOM_UI=1 to enable the custom UI

Replace the stock Kratos self-service UI for the login flow with a custom
@ory/elements-react page at /login, styled with the dashboard's own design
system (no Ory default theme CSS).

- add @ory/elements-react + @ory/nextjs deps
- src/configs/ory.ts: OryClientConfiguration (login-only scope)
- src/app/login: server page (getLoginFlow + per-request sdk.url = own origin),
  client <Login> wrapper, layout, and component overrides for card/header/
  input/button/label/sso/form-group/message
- src/proxy.ts: wire @ory/nextjs createOryMiddleware so /self-service etc.
  proxy to Kratos same-origin (first-party cookies, no CORS)
- env: NEXT_PUBLIC_ORY_SDK_URL (browser-facing Kratos public URL)
@cla-bot cla-bot Bot added the cla-signed label Jun 10, 2026
@vercel

vercel Bot commented Jun 10, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
web Error Error Jun 11, 2026 2:28pm
web-juliett Error Error Jun 11, 2026 2:28pm

Request Review

- Add svgl Google/GitHub logos via shadcn registry (@svgl)
- Show provider logo + 'Continue with …' on Ory SSO buttons
- GitHub logo toggles light/dark variants via Tailwind dark: variant
- Replace bare login divider with an 'or' separator
- Resolve provider via node.attributes.value (stable id) so the logo +
  'Continue with …' label render (the provider prop is a display label)
- Remove logos/index.ts barrel in favor of direct imports
- Add /register route (page, layout, card) mirroring /login, using
  @ory/elements-react <Registration> + getRegistrationFlow
- Make shared card header/footer/submit-button flow-aware (Sign in vs
  Sign up; registration footer shows sign-in link + legal copy)
- Enable registration in ory config, point registration_ui_url at /register
huv1k added 2 commits June 10, 2026 16:26
- Add 'Don't have an account? Sign up' footer to the login card (-> /register)
- Move GitHubLogo into oauth-provider-buttons and drop the shared
  logos/github-logo.tsx; remove GitHub from the Ory SSO provider map
Rename /register route to /registration and update registration_ui_url
and the login footer 'Sign up' link to match.
…to Ory proxy

- Drop logos/google-logo.tsx wrapper; oauth-provider-buttons and the Ory
  SSO button now use the @svgl Google icon directly with sizing
- Pass oryConfig.project to createOryMiddleware so Kratos redirect/body
  URLs are rewritten onto our /login and /registration UIs
- Add isOryCustomUiEnabled() (Ory enabled && VERCEL_ENV !== production)
- Gate the proxy Ory-SDK short-circuit and the /login + /registration
  pages on it; production keeps Ory auth on but uses its existing flow
- Drop redundant component-label comments, keeping only core why-notes
proxy.ts now imports createOryMiddleware (@ory/nextjs/middleware), whose
ESM bare-imports 'next/server'; inline it like next-auth so vitest resolves
Next's exports. Fixes proxy.test.ts and auth-ory-entrypoints.test.ts.
Replace the VERCEL_ENV-based gate with an explicit NEXT_PUBLIC_ORY_CUSTOM_UI flag.
isOryCustomUiEnabled() is read in Edge middleware, where VERCEL_ENV/AUTH_PROVIDER
are inlined at build time and resolve to undefined in standalone/self-hosted
builds (anything but `next dev`) — silently disabling the same-origin Ory proxy.
A NEXT_PUBLIC_ flag is readable at request time everywhere and lets the local
harness opt in explicitly while production leaves it unset (keeping /sign-in).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 16c3e0b9bb

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread src/proxy.ts
Comment on lines +131 to +132
if (isOryCustomUiEnabled() && isOrySdkProxyPath(request.nextUrl.pathname)) {
return oryProxy(request)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Move the Ory proxy check before the public-route bypass

When the custom UI submits or creates flows under /self-service/..., classifyProxyRequest marks those paths as public with needsOryAuthJsSession: false (checked in src/core/server/http/proxy-plan.ts), so the earlier return proxyCore(request, plan) runs before this new Ory proxy branch is reached. In the staging/preview custom-UI context, requests like /self-service/login/browser therefore fall through to Next instead of being forwarded to Kratos, breaking login/registration flow creation and form submission; this branch needs to run before the !plan.needsOryAuthJsSession bypass.

Useful? React with 👍 / 👎.

…pass

/self-service/* classifies as public (needsOryAuthJsSession: false), so the
short-circuit ran too late and those requests fell through to Next, breaking
custom-UI flow creation and form submits. Move the gated Ory-SDK proxy branch
to the top of proxy(). Add a regression test covering both enabled/disabled.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants