feat: integrate @zitadel/tanstack-auth#2
Merged
Conversation
- 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
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).
- 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.
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
…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.
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.
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.
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.
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.
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.
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.
Documents the @auth/core v5 base URL env var. Aligns with the other example apps so users get a consistent .env across all 8.
…viders
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.
Aligns with the other 7 examples; .env.example already had it.
…shed 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.
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
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.
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.
761f261 to
53fd4ad
Compare
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.
…mp typescript-eslint to ^8.60
… 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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
Migrates this example to the published
@zitadel/tanstack-authSDK, replacing the hand-rolled auth wiring with the SDK's factory pattern (handlers,getSession,signIn,signOut,signInUrl,signOutUrl). Switches the dependency fromfile:../tanstack-authto the npm-published version and aligns the/auth/login,/auth/error, and/profileroutes with the rest of the SDK family. Dropped the~/auth.serverimport from the/profileloader — tanstack-start's import-protection plugin rejects**/*.server.*imports from route files.Related Issue
N/A — part of the family-wide migration to the
@zitadel/*-authSDKs.Motivation and Context
The example previously hand-rolled OIDC/PKCE wiring on top of
@auth/coreand pulled the SDK via afile:link. Both go away once@zitadel/tanstack-authis published: the example becomes a small consumer of the SDK and any consumer can copy it without local checkouts. This also brings the example's env vars (AUTH_URL,AUTH_SECRET, callback paths under/api/auth) in line with the other 7 example apps.How Has This Been Tested?
Locally via
devbox:npm run lint,npm run format:check,npm run prepack(typecheck), andnpm run buildall pass. Playwright E2E suite runs in CI. Manual smoke: full login →/profile→ logout against a real Zitadel instance.Documentation:
N/A — README updates for the new SDK shape landed in earlier commits on this branch.
Checklist: