Commit a763eb1
authored
feat: integrate @zitadel/tanstack-auth (#2)
* feat: initial example-tanstack-start-auth app with @zitadel/tanstack-start-auth
* fix: align scaffold files for byte-level parity with other example repos
- Add missing .editorconfig
- Update devbox.lock to Node.js 22.22.0 (matches other repos)
- Fix .gitignore: remove .astro/.svelte-kit/dist artifacts, add .vinxi for Vinxi build
- Fix .prettierignore: remove .astro (not used by TanStack Start)
- Fix .env.example: correct framework name in comment
- Fix playwright.config.ts: align ZITADEL_CALLBACK_URL to match other repos
- Fix README.md: comprehensive documentation matching pattern of other examples
- Fix knip.config.js: add TanStack Start-specific entry points
* fix: add tailwind CSS entry file and align scaffold files with parity standards
* fix: route devcontainer commands through devbox to use project-local npm cache
* fix: update import to renamed @zitadel/tanstack-auth package
* fix: add depcheck script
* chore: bump prettier-plugin-tailwindcss to 0.6.14
* chore: add trailing newline to .nvmrc
* chore: add .devbox/ to .gitignore
* chore: switch to file: reference to generate package-lock.json
* feat: integrate @zitadel/tanstack-auth and refactor auth routes
* fix: migrate to @tanstack/react-start v1.168 vite-based API
The installed @tanstack/react-start v1.168 no longer exports
'@tanstack/react-start/config' or '@tanstack/react-start/api', and
Meta/Scripts/StartClient are gone. This commit aligns the example with
the new vite-plugin-based API the SDK playground uses:
- Replace vinxi + app.config.ts with vite.config.ts using
tanstackStart({srcDirectory:'app'}) and @vitejs/plugin-react.
- Rewrite app/server.tsx to use createStartHandler + createServerEntry
and inline the /api/auth/* and /api/userinfo handlers (replacing the
removed createAPIFileRoute pattern).
- Rewrite app/client.tsx to hydrateStart() and app/router.tsx to use
createRouter with routeTree.gen.
- Add app/session.ts with a createServerFn-based fetchSession helper.
- Switch /profile to a loader-based pattern using fetchSession.
- Drop validateSearch from /auth/login to avoid the search-param
redirect loop (matches SDK playground).
- Move Session/JWT augmentation into app/types/auth.d.ts and add
tsconfig paths to dedup @auth/core.
- Turn off plain no-unused-vars in eslint config (it false-positives
on TS function-type parameter names).
- Clean up knip.config (drop stale entries, ignore routeTree.gen.ts).
* fix: ignore generated routeTree.gen.ts in prettier; gitignore dist
- Add app/routeTree.gen.ts to .prettierignore so format:check no
longer fails on the auto-generated route tree.
- Add dist/ to .gitignore and remove the previously-committed build
output. The build is reproducible from source.
* chore: align with @zitadel/tanstack-auth directory rename
The sibling SDK directory was renamed from tanstack-start-auth to
tanstack-auth; this commit updates the example's package metadata to
match:
- package.json — name now matches the SDK family
- @zitadel/tanstack-auth file: link points at ../tanstack-auth (was
../tanstack-start-auth)
- regenerated package-lock.json
* fix: redirect /api/auth/logout/callback to /logout/success and clear logout_state cookie
The handler in app/server.tsx was redirecting to /, but the test in
test/app.spec.ts expects /logout/success — which is the actual page
the user should land on after RP-initiated logout completes. It also
wasn't clearing the logout_state cookie. logout_state is set with
Path=/api/auth/logout/callback when the logout flow starts, so the
clearing Set-Cookie needs the same Path attribute or the browser
will retain the cookie.
* fix: mount React via hydrateRoot in client entry
The previous client.tsx only called hydrateStart() from
@tanstack/react-start/client, which initialises the TanStack router
but does not mount React. As a result, no useEffect ran and no event
handlers attached — every page was static HTML pretending to be a
React app. SSR worked and every test that only clicked plain anchors
continued to pass, hiding the bug.
Switches client.tsx to the canonical TanStack Start template:
hydrateRoot(document, <StrictMode><StartClient /></StrictMode>) inside
a startTransition. StartClient internally calls hydrateStart, so this
is a strict superset of the previous behaviour.
* fix(auth/login): submit POST form with CSRF token
The previous anchor pointed at GET /api/auth/signin/zitadel which
Auth.js rejects (the per-provider endpoint requires a POST with a
CSRF token); the user landed on /auth/error?error=Configuration on
click. Matches the pattern used by example-sveltekit-auth and
example-solidstart-auth: fetch /api/auth/csrf in useEffect, populate
a hidden input, submit a POST form to /api/auth/signin/zitadel.
* refactor(profile): redirect unauth users via SDK signInUrl
Replaces the hardcoded throw redirect({ to: '/auth/login' }) with
throw redirect({ href: signInUrl({ redirectTo: '/profile' }) }).
signInUrl is the canonical way to encapsulate the sign-in URL
across the SDK family.
* fix(logout/callback): validate state before clearing cookies
The previous handler cleared authjs.* cookies and the logout_state
cookie unconditionally on any GET /api/auth/logout/callback. The
other seven example apps validate the `state` query parameter
against the `logout_state` cookie before clearing — preventing an
attacker from triggering an unwanted logout via a crafted link.
Brings tanstack into parity with the other seven by:
1. Reading `state` from the URL and `logout_state` from the cookie jar
2. Only clearing cookies when both are present and match
3. Otherwise redirecting to /logout/error?reason=Invalid+or+missing+state+parameter.
4. Adding Clear-Site-Data: "cookies" + HttpOnly; SameSite=Lax on the
logout_state expiry header to match the other seven verbatim
The existing app.spec.ts already exercises the success path
(provides state + matching cookie) and continues to pass.
* fix(server): implement POST /api/auth/logout properly
Previously stubbed with 405 ("back-channel logout not implemented"),
so clicking SignOutButton hit a dead endpoint and the user never
left the app. Replaces the stub with the same handler the other
seven examples use: load session, generate state, set
Path-scoped logout_state cookie, redirect to Zitadel's
end_session_endpoint. The /api/auth/logout/callback handler then
validates state on return (already in place from the prior commit).
Verified in-browser via the Sign out button: now redirects through
Zitadel and lands on /logout/success with cookies cleared.
* fix(logout): add conditional Secure flag to logout_state cookie
The logout_state cookie was being set without the Secure flag in any
environment. In production (HTTPS) the CSRF cookie should always be
Secure; in dev (HTTP) it must be unset so the browser doesn't drop
the cookie and silently break the state round-trip.
Apply the same conditional pattern used by example-remix-auth.
* chore(env): add AUTH_URL to .env.example
Documents the @auth/core v5 base URL env var. Aligns with the other
example apps so users get a consistent .env across all 8.
* feat(auth/login): render provider name dynamically from /api/auth/providers
Match the dynamic-provider pattern used by the other examples: fetch
/api/auth/providers + /api/auth/csrf in parallel, render the form
with provider.signinUrl and "Sign in with {provider.name}" instead
of hardcoded values.
* chore(playwright): add AUTH_URL to test env
Aligns with the other 7 examples; .env.example already had it.
* chore(deps): switch @zitadel/tanstack-auth@^1.0.0 from file: to published version
Switch package.json from file:../tanstack-auth to ^1.0.0 (now
published on npm). Also drop the signInUrl import from
~/auth.server in profile.tsx — tanstack-start's import-protection
plugin rejects `**/*.server.*` imports from route files (since
routes run in both environments), and signInUrl was only used to
build a static redirect URL. Use a route-relative redirect instead.
* chore: trigger CI
* fix(deps): regenerate lockfile with linux-x64 optional binaries
Adds cross-platform install entries for native deps (@rollup,
@oxc-resolver, @oxc-parser) so 'npm ci' on Linux runners finds the
linux-x64-gnu binaries. The previous lockfile was generated on
darwin-arm64 only, omitting the Linux entries; CI hit npm/cli#4828
and refused to install them.
Regenerated via:
npm install --include=optional --os=linux --cpu=x64 --package-lock-only
npm install --include=optional
* chore(devcontainer): decouple from devbox
devbox is a local-only nix-based package manager; coupling it to the
devcontainer image (which already provides Node via the base image)
creates an unnecessary dependency for contributors using the
devcontainer. Remove the devbox feature and use plain npm ci /
playwright install instead.
* chore(knip): drop stale unresolved:off rule + SDK ignore
The unresolved:off rule was added for an earlier tanstack-router issue that is no longer reproducible. The SDK ignore was always wrong since the SDK is imported by app code.
* chore: bump tailwind to ^4.3 + prettier plugin to ^0.8 + README v22
* chore: add X-Content-Type-Options + tsconfig moduleResolution PascalCase
* feat(api): add /api/userinfo handler in server.tsx
Adds a /api/userinfo GET handler to the server entry's path-match
chain. Mirrors the other 7 examples; sits next to the existing
/api/auth/* and /api/(un)?protected blocks. TanStack Start doesn't
have a file-based API route convention so this lives inline.
* docs: add JSDoc on auth helpers + callbacks (parity with astro/nuxt/sveltekit/qwik)
* chore: default callbackUrl to /profile + drop unused demo routes + bump typescript-eslint to ^8.60
* fix(deps): pin @auth/core to ^0.40.0 to match SDK (dedupe single copy)
* fix: use client signIn helper on home and bump @zitadel/tanstack-auth to 1.1.2
The home Login button hardcoded a GET to /api/auth/signin/zitadel, which
Auth.js v5 rejects with a Configuration error. Use the SDK client signIn
helper (CSRF+POST) and bump the SDK to the version that ships the fix.1 parent 13d06c2 commit a763eb1
59 files changed
Lines changed: 9283 additions & 0 deletions
File tree
- .devcontainer
- .github
- workflows
- app
- components
- lib
- routes
- auth
- logout
- types
- public
- test
Some content is hidden
Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
0 commit comments