{"_id":"@bakaburg24/identity-ui","_rev":"6-f7ab0b42495dd3aa51db0b1666f9fb1a","name":"@bakaburg24/identity-ui","dist-tags":{"latest":"0.10.0"},"versions":{"0.4.0":{"name":"@bakaburg24/identity-ui","version":"0.4.0","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"author":{"name":"bakaburg24"},"license":"MIT","_id":"@bakaburg24/identity-ui@0.4.0","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"homepage":"https://github.com/bakaburg24/solana-shuffle/tree/main/packages/identity-ui#readme","bugs":{"url":"https://github.com/bakaburg24/solana-shuffle/issues"},"bin":{"identity-ui":"bin/init.mjs"},"dist":{"shasum":"319459f0735b18126eca28d75dd5777571a43265","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.4.0.tgz","fileCount":101,"integrity":"sha512-LaBRhfaZ9xecIu4/WICs+JQsTQ39fJ2KiHParvlStU9Vp4TTc8MDroTz/hI0f2XJILFLTIZGg6G3Rrf6hzbbng==","signatures":[{"sig":"MEQCIEGQzweMYRUaYxntEwsQdliOwYzhGUQYyybOXaVtSxg1AiBxL4sjdNgPmEoeMZ14q0mUvNZdykEA2iLgLzTuZ5yQ5g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156117},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./styles.css":"./src/styles.css","./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"}},"gitHead":"b283475b81997dcfb1cfe8fe3e2f517b53bd6923","private":false,"scripts":{"build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"repository":{"url":"git+https://github.com/bakaburg24/solana-shuffle.git","type":"git","directory":"packages/identity-ui"},"_npmVersion":"11.5.1","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","directories":{},"_nodeVersion":"24.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5","@types/react":"^19","@types/react-dom":"^19"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/identity-ui_0.4.0_1778290883582_0.1378204636575977","host":"s3://npm-registry-packages-npm-production"}},"0.4.1":{"name":"@bakaburg24/identity-ui","version":"0.4.1","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"author":{"name":"bakaburg24"},"license":"MIT","_id":"@bakaburg24/identity-ui@0.4.1","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"homepage":"https://github.com/bakaburg24/solana-shuffle/tree/main/packages/identity-ui#readme","bugs":{"url":"https://github.com/bakaburg24/solana-shuffle/issues"},"bin":{"identity-ui":"bin/init.mjs"},"dist":{"shasum":"e6552fbbcbc469a196e30c05bf7480af1d35cc4d","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.4.1.tgz","fileCount":101,"integrity":"sha512-5zHeRk6j6hqp21SSqMdHCoT2fhSUiFLBbMngU+w5ehgFs6LySoeeBOCxGiAxZ1W7X6C0r0lSglJ+ifCly2UVlA==","signatures":[{"sig":"MEYCIQCj+/NzsH+VrGtkzsKvanqifRrmHF/73ivBYCjOzEGcKQIhAOCY7WTHGPtP5eNj2HCqkp0y7mZdpblKnC9iBbL4mg8R","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":156653},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./styles.css":"./src/styles.css","./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"}},"gitHead":"b283475b81997dcfb1cfe8fe3e2f517b53bd6923","private":false,"scripts":{"build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"repository":{"url":"git+https://github.com/bakaburg24/solana-shuffle.git","type":"git","directory":"packages/identity-ui"},"_npmVersion":"11.5.1","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","directories":{},"_nodeVersion":"24.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5","@types/react":"^19","@types/react-dom":"^19"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/identity-ui_0.4.1_1778293448843_0.8169041295753972","host":"s3://npm-registry-packages-npm-production"}},"0.6.0":{"name":"@bakaburg24/identity-ui","version":"0.6.0","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"author":{"name":"bakaburg24"},"license":"MIT","_id":"@bakaburg24/identity-ui@0.6.0","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"homepage":"https://github.com/bakaburg24/solana-shuffle/tree/main/packages/identity-ui#readme","bugs":{"url":"https://github.com/bakaburg24/solana-shuffle/issues"},"bin":{"identity-ui":"bin/init.mjs"},"dist":{"shasum":"3cf7635036d39299a188a1f12af47ac5da503fab","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.6.0.tgz","fileCount":109,"integrity":"sha512-59dYwoyuhNqHAJmnjO0sdeSb6mo7x/r0vf/vBet3Gylcxtewi9OUkQmYVRkkjldUK6uIVcF+o7C3SENt0QX0gg==","signatures":[{"sig":"MEUCIEQ3LMESXVuMxeo+Jyt2EyPC2X/4kdxeKdRHuyUKJEdGAiEA7jlmItIRoSN/QAa59BUu6t8S96QTVc+mMhMdZW9dxLc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":202193},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./styles.css":"./src/styles.css","./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"}},"gitHead":"5a1e3a5885f200ef8edc4bee3b881864e7f02fd3","private":false,"scripts":{"build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"repository":{"url":"git+https://github.com/bakaburg24/solana-shuffle.git","type":"git","directory":"packages/identity-ui"},"_npmVersion":"11.5.1","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","directories":{},"_nodeVersion":"24.5.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5","@types/react":"^19","@types/react-dom":"^19"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/identity-ui_0.6.0_1779208519181_0.7372389611229053","host":"s3://npm-registry-packages-npm-production"}},"0.7.0":{"name":"@bakaburg24/identity-ui","version":"0.7.0","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"author":{"name":"bakaburg24"},"license":"MIT","_id":"@bakaburg24/identity-ui@0.7.0","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"homepage":"https://github.com/bakaburg24/solana-shuffle/tree/main/packages/identity-ui#readme","bugs":{"url":"https://github.com/bakaburg24/solana-shuffle/issues"},"bin":{"identity-ui":"bin/init.mjs"},"dist":{"shasum":"14b8445a6e58a46386972ce04cbce1e2d0f8d0b5","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.7.0.tgz","fileCount":121,"integrity":"sha512-q4qnlK+8QnVp29bRH283Qv/C6C+I2U9Iv6G2wCEEIiNxomsYYuzjX1yGz7MqWlkRZu0tc900Vx/OOOK1jvMLzg==","signatures":[{"sig":"MEUCIQCKdXoE9phL02P9PM/fD2Ovl3POXen9NHV2WZ5fLunlRwIgHRVUoXL8M+yvOEOa1RskALBBNQhYG7EwEfcckTLI4jQ=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":241634},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./styles.css":"./src/styles.css","./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"},"./next/middleware":{"types":"./dist/next-middleware.d.ts","import":"./dist/next-middleware.js"}},"gitHead":"28f538bd25d0a880795a46ec98c0ae457f5e15a9","private":false,"scripts":{"build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"repository":{"url":"git+https://github.com/bakaburg24/solana-shuffle.git","type":"git","directory":"packages/identity-ui"},"_npmVersion":"11.5.1","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"jose":"^5.10.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5","@types/react":"^19","@types/react-dom":"^19"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/identity-ui_0.7.0_1780148933821_0.3057178974793606","host":"s3://npm-registry-packages-npm-production"}},"0.9.0":{"name":"@bakaburg24/identity-ui","version":"0.9.0","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"author":{"name":"bakaburg24"},"license":"MIT","_id":"@bakaburg24/identity-ui@0.9.0","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"homepage":"https://github.com/bakaburg24/identity/tree/main/packages/identity-ui#readme","bugs":{"url":"https://github.com/bakaburg24/identity/issues"},"bin":{"identity-ui":"bin/init.mjs"},"dist":{"shasum":"ee8cd10600dc975f475db14919fdc85661e36cfd","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.9.0.tgz","fileCount":126,"integrity":"sha512-G/5HNroo6h7pLeMWqtDbKywp2/7cnCLtu6Da+/KGoL544O3hg14YFzMJfbyJnzJpcTLfG5xZkBvKzl0b1Wck7w==","signatures":[{"sig":"MEUCIHRRfkxrZA05uI0w3glAKD3V09pQZ2FULc/D57RaFXp5AiEAxKAheDyX8rZnyHo64wPoDgHUkiQcu9P2yaXfmlXPR4Y=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":272170},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./styles.css":"./src/styles.css","./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"},"./next/middleware":{"types":"./dist/next-middleware.d.ts","import":"./dist/next-middleware.js"}},"gitHead":"fbbb70418df02ba8465f68518978603a18879bcb","private":false,"scripts":{"build":"tsc","clean":"rm -rf dist","typecheck":"tsc --noEmit"},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"repository":{"url":"git+https://github.com/bakaburg24/identity.git","type":"git","directory":"packages/identity-ui"},"_npmVersion":"11.5.1","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","directories":{},"_nodeVersion":"24.5.0","dependencies":{"jose":"^5.10.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5","@types/react":"^19","@types/react-dom":"^19"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"_npmOperationalInternal":{"tmp":"tmp/identity-ui_0.9.0_1783413179109_0.4192090022767261","host":"s3://npm-registry-packages-npm-production"}},"0.10.0":{"name":"@bakaburg24/identity-ui","version":"0.10.0","description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"homepage":"https://github.com/bakaburg24/identity/tree/main/packages/identity-ui#readme","repository":{"type":"git","url":"git+https://github.com/bakaburg24/identity.git","directory":"packages/identity-ui"},"bugs":{"url":"https://github.com/bakaburg24/identity/issues"},"author":{"name":"bakaburg24"},"license":"MIT","private":false,"publishConfig":{"access":"public"},"type":"module","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"identity-ui":"bin/init.mjs"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js"},"./next":{"types":"./dist/next.d.ts","import":"./dist/next.js"},"./next/handlers":{"types":"./dist/handlers/index.d.ts","import":"./dist/handlers/index.js"},"./next/middleware":{"types":"./dist/next-middleware.d.ts","import":"./dist/next-middleware.js"},"./styles.css":"./src/styles.css"},"scripts":{"build":"tsc","typecheck":"tsc --noEmit","clean":"rm -rf dist","prepublishOnly":"npm run clean && npm run build"},"dependencies":{"jose":"^5.10.0"},"peerDependencies":{"next":"^15.0.0 || ^16.0.0","react":"^18.2.0 || ^19.0.0","react-dom":"^18.2.0 || ^19.0.0"},"peerDependenciesMeta":{"next":{"optional":true}},"devDependencies":{"@types/react":"^19","@types/react-dom":"^19","next":"^16.2.4","react":"^19.2.5","react-dom":"^19.2.5","typescript":"^5"},"_id":"@bakaburg24/identity-ui@0.10.0","gitHead":"996a75f255ccedf0015a6595fd5cb2bc9b425b4b","_nodeVersion":"24.5.0","_npmVersion":"11.5.1","dist":{"integrity":"sha512-ylBrEymXv2RdM1I/KffkBftRo2RrBdcOvfDqulrYw+6NVcz5iK7Ke1h8LYkFomUBjrwzTaBn8Z8WcHyrKR7u/g==","shasum":"076334b07fcce4142aa109ff993950f539bf37ba","tarball":"https://registry.npmjs.org/@bakaburg24/identity-ui/-/identity-ui-0.10.0.tgz","fileCount":126,"unpackedSize":274583,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIFXLwA2Rt4fD7CgXsjtZl+UlSBoRRtNtJ5pOuzPcba9nAiBtS2fpstwx1B79W+VpdC7+Mx5RkiHQjIHcul6AqtD+hA=="}]},"_npmUser":{"name":"bakaburg24","email":"bakaburg24@gmail.com"},"directories":{},"maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/identity-ui_0.10.0_1785167270197_0.1469512529297865"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-09T01:41:23.468Z","modified":"2026-07-27T15:47:50.471Z","0.4.0":"2026-05-09T01:41:23.727Z","0.4.1":"2026-05-09T02:24:08.987Z","0.6.0":"2026-05-19T16:35:19.344Z","0.7.0":"2026-05-30T13:48:53.970Z","0.9.0":"2026-07-07T08:32:59.274Z","0.10.0":"2026-07-27T15:47:50.345Z"},"bugs":{"url":"https://github.com/bakaburg24/identity/issues"},"author":{"name":"bakaburg24"},"license":"MIT","homepage":"https://github.com/bakaburg24/identity/tree/main/packages/identity-ui#readme","keywords":["auth","authentication","identity","nextjs","react","drop-in","supabase-alternative","clerk-alternative","auth-ui","oauth","magic-link","solana","self-hosted","multi-tenant"],"repository":{"type":"git","url":"git+https://github.com/bakaburg24/identity.git","directory":"packages/identity-ui"},"description":"Drop-in auth for any Next.js app. One init command, one config file, full feature set: login / signup / forgot / reset / magic-link / OAuth / Solana wallet. Calls a self-hosted identity service (Rust); no Supabase / Clerk / Auth0 lock-in.","maintainers":[{"name":"bakaburg24","email":"bakaburg24@gmail.com"}],"readme":"# @bakaburg24/identity-ui\n\n> **Drop-in auth for Next.js apps.** One init command, one config file, full feature set. Use it instead of Supabase Auth, Clerk, or Auth0 — until you're ready to migrate, or forever.\n\n```bash\npnpm add @bakaburg24/identity-ui\nnpx @bakaburg24/identity-ui init\n# done. /auth/login + /auth/signup + /auth/forgot + /auth/reset all work.\n```\n\nThat's the whole setup. Three files get scaffolded. You edit one of them to set your brand color and pick which auth methods to expose. Login, signup, forgot-password, password-reset, magic-link, OAuth (Google/Apple), Solana wallet sign-in — all wired and styled out of the box.\n\n## Why this exists\n\nMost Next.js apps eventually need auth. The current options are:\n\n- **Supabase / Clerk / Auth0** — fast to integrate, but you're locked into their pricing, their policies, and their data plane. Hard to leave.\n- **NextAuth / Auth.js** — flexible, but you write the UI yourself. Login forms, signup pages, forgot-password flows — all on you.\n- **Roll your own** — full control, but you'll spend weeks rebuilding what others have already gotten right.\n\nThis package sits between them. The UI + the API plumbing live in npm, the auth backend is your own Rust service ([source](../../services/identity/) — Apache-2.0). Migrate to a managed provider whenever you want; you own the user data.\n\n## Feature parity table\n\n| Feature | This package | NextAuth | Clerk | Supabase Auth |\n|---|---|---|---|---|\n| Email + password | ✅ | ✅ | ✅ | ✅ |\n| Forgot password (UI + flow) | ✅ | ❌ (DIY) | ✅ | ✅ |\n| Magic link | ✅ | ✅ | ✅ | ✅ |\n| OAuth (Google / Apple / etc.) | ✅ | ✅ | ✅ | ✅ |\n| Solana wallet sign-in | ✅ | ❌ | ❌ | ❌ |\n| Multi-tenant | ✅ | ⚠️ (via custom adapters) | ✅ (paid) | ⚠️ (RLS DIY) |\n| Self-hosted | ✅ | ✅ | ❌ | ⚠️ (paid tier) |\n| Brand-themable UI | ✅ | ❌ (DIY) | ✅ | ⚠️ |\n| Drop-in scaffold CLI | ✅ | ❌ | ❌ | ❌ |\n| Cost at 100k MAU | $0 + your infra | $0 | ~$1k/mo | ~$25/mo |\n| Cost at 1M MAU | $0 + your infra | $0 | ~$10k/mo | ~$600/mo |\n| Lock-in | None — your DB | None | High | Medium |\n\n## How auth gets added to a new Next.js app\n\n### 0. Install + init\n\n```bash\npnpm add @bakaburg24/identity-ui\nnpx @bakaburg24/identity-ui init\n```\n\nThis creates six files:\n- `lib/auth-config.ts` — brand + methods + handler config\n- `lib/auth-client.ts` — thin browser fetch wrapper\n- `app/auth/[[...segment]]/page.tsx` — UI catch-all (1-line re-export)\n- `app/api/auth/[...path]/route.ts` — API catch-all (3 lines)\n- `middleware.ts` — refreshes the access token on navigation\n- `app/providers.tsx` — mounts the client-side silent-refresh scheduler\n\nIdempotent — re-running skips files that already exist (`--force` to overwrite).\n\n### 1. Mount the provider in your layout\n\n```tsx\n// app/providers.tsx (or app/layout.tsx)\n\"use client\";\nimport { IdentityUIProvider } from \"@bakaburg24/identity-ui\";\nimport \"@bakaburg24/identity-ui/styles.css\";\nimport { authConfig } from \"../lib/auth-config\";\n\nexport function Providers({ children }: { children: React.ReactNode }) {\n  return <IdentityUIProvider config={authConfig}>{children}</IdentityUIProvider>;\n}\n```\n\n### 2. Set env vars\n\n```\nIDENTITY_BASE_URL=https://identity.your-host.com\nIDENTITY_TENANT_SLUG=your-tenant\n```\n\n`IDENTITY_BASE_URL` points at the [identity service](../../services/identity/) — your own deploy or someone else's hosted instance. `IDENTITY_TENANT_SLUG` defaults to `mailflix` so override it.\n\n### 3. Edit `lib/auth-config.ts`\n\nSet your brand accent color, optional logo JSX, and which auth methods to expose:\n\n```ts\nimport type { IdentityUIConfig } from \"@bakaburg24/identity-ui\";\nimport { authClient } from \"./auth-client\";\n\nexport const authConfig: IdentityUIConfig = {\n  client: authClient,\n  brand: {\n    name: \"My App\",\n    accent: \"#6366f1\",\n    // logo: <YourBrandMark />,\n    // tagline: \"...\",\n  },\n  methods: [\"password\", \"oauth-google\", \"magic-link\"],\n  defaultRedirect: \"/dashboard\",\n};\n```\n\nDone. Visit `/auth/login`, `/auth/signup`, `/auth/forgot`, `/auth/reset?token=…`.\n\n### 4. Verify with `doctor`\n\n```bash\nnpx @bakaburg24/identity-ui doctor\n```\n\nChecks the whole integration without touching real credentials: the\ndependency, all six scaffolded files, `<Providers>` + `styles.css`\nwired into `app/layout.tsx`, env vars (process env or `.env*` files),\nidentity-service reachability (`GET /health`), and that your tenant\nslug actually resolves (`GET /v1/identity/tenant`). Exits non-zero on\nfailure, so it doubles as a CI/deploy gate:\n\n```bash\nnpx @bakaburg24/identity-ui doctor --base-url=https://id.example.com --tenant=acme\n```\n\n## URL surface\n\nThe package owns these routes — no consumer code needed beyond the four scaffolded files.\n\n| URL | View / Endpoint |\n|---|---|\n| `/auth` or `/auth/login` | LoginForm |\n| `/auth/signup` | SignupForm |\n| `/auth/forgot` | ForgotForm (sends reset email) |\n| `/auth/reset?token=...` | ResetForm (token from email) |\n| `POST /api/auth/login` | identity `/auth/email/login` + sets session cookies |\n| `POST /api/auth/signup` | identity `/auth/email/signup` + sets session cookies |\n| `POST /api/auth/logout` | identity `/auth/logout` + clears session cookies |\n| `POST /api/auth/refresh` | identity `/auth/refresh` + rotates session cookies |\n| `GET /api/auth/me` | current user (from cookie) |\n| `POST /api/auth/forgot` | identity `/auth/email/forgot` |\n| `POST /api/auth/reset` | identity `/auth/email/reset` |\n\nBoth `/auth/*` and `/api/auth/*` are catch-alls. Adding new flows in the package exposes them everywhere on the next `pnpm install`.\n\n## Auth methods\n\nToggle via the `methods` array in `lib/auth-config.ts`:\n\n| Method | Description | Scaffolded? |\n|---|---|---|\n| `password` | Email + password (always available) | ✅ fully wired by `init` |\n| `magic-link` | Passwordless email sign-in / sign-up | ✅ fully wired by `init` (v0.5.0+) |\n| `oauth-google` | \"Continue with Google\" button | ⚠️ button + start leg (v0.5.0+) — see the OAuth return leg note |\n| `oauth-discord` | \"Continue with Discord\" button | ⚠️ button + start leg — see the OAuth return leg note |\n| `oauth-linkedin` | \"Continue with LinkedIn\" button | ⚠️ button + start leg (v0.10.0+) — see the OAuth return leg note |\n| `oauth-apple` | \"Continue with Apple\" button | ⚠️ button only — needs an identity Apple provider (not built server-side yet) |\n| `wallet` | Solana wallet sign-in | ⚠️ stub — implement `signInWithWallet` against your wallet-adapter flow |\n\n> **The OAuth return leg is not scaffolded.** This applies to every OAuth\n> provider, not just LinkedIn, and it predates 0.10.0.\n>\n> `init` gives you the outbound half: the button, and the `oauth/<provider>`\n> case in the API catch-all that 307s to identity. It also gives you the\n> server-side redemption handler (`oauth/exchange`). What it does **not** give\n> you is the client-side page that reads `?exchange_code=…` off the URL when\n> identity redirects the browser back, and POSTs it to `/api/auth/oauth/exchange`.\n> Nothing under `src/` reads that parameter.\n>\n> Until you write that page, the OAuth round trip ends with the member back on\n> your site holding an unredeemed code and no session. Two things to get right\n> in it: `return_to` must be an **absolute** URL on your own origin — the\n> default `\"/\"` is relative and identity's 303 resolves it against *identity's*\n> origin — and the origin must be in the tenant's `allowed_origins`.\n>\n> `apps/identity-portal` in this repo has a working implementation to copy.\n\n`password`, `magic-link`, and `oauth-google` work with **zero extra code** — the scaffolded `auth-client.ts` + the catch-all handler implement them end to end against the identity service. `wallet` still needs a consumer-supplied `signInWithWallet` (wallet-adapter UI is app-specific). `oauth-apple` renders a button but identity has no Apple provider yet, so leave it out of `methods` until that ships.\n\n### Magic-link: how the two halves fit\n\n`magic-link` is a two-step flow and the scaffold handles both:\n\n1. **Request** — the user enters their email on the sign-in page; `signInWithMagicLink` POSTs to `/api/auth/magic-link/request`. Identity emails a one-shot link. Response is always 204 (no account enumeration). If the email is new, the account is created when the link is consumed — first link doubles as sign-up.\n2. **Consume** — the emailed link points at `/auth/magic-link/<token>` in *your* app. The scaffolded catch-all page reads the token and renders `<MagicLinkConsume>`, which auto-submits it to `/api/auth/magic-link/consume`. On success the session cookie is set and `onAuthenticated` fires — same as a password login. No user input on the consume page; it just shows \"signing you in…\".\n\nYou don't write any of this — `init` scaffolds it. `<MagicLinkConsume>` is also exported standalone if you want a custom consume page (see Escape hatches).\n\n## Handling auth errors (account status, step-up)\n\nThe identity service uses **prefix-coded error messages** for conditions a login UI should treat specially. These come back with HTTP `400` and `error.code === \"BAD_REQUEST\"`, but the `error.message` starts with a stable, machine-matchable prefix. The package surfaces both `code` and `message` through the thrown error (`IdentityHandlerError` server-side; your `auth-client.ts` sees the JSON body) — match on the **message prefix**, not the HTTP code.\n\n| Message prefix | When | What your UI should do |\n|---|---|---|\n| `ACCOUNT_SUSPENDED:` | The operator suspended this user (e.g. lapsed subscription). Auth is blocked until reactivated. | Show \"your access is suspended — contact &lt;operator&gt;\", not a generic \"wrong password\". Don't offer retry. |\n| `ACCOUNT_DISABLED:` | Harder stop (banned / closed). | Show \"this account has been disabled\". Terminal — no retry, no self-serve. |\n| `STEP_UP_REQUIRED:` | Switching into a high-trust (operator-custody) tenant needs password re-entry. | Prompt for the password, retry `switch-tenant` with `current_password` set. |\n| `STEP_UP_BAD_PASSWORD:` | Step-up password was wrong. | Inline \"incorrect password, try again\" on the step-up prompt. |\n| `STEP_UP_NO_PASSWORD:` | Wallet/OAuth-only account can't satisfy step-up. | Tell them this tenant requires a password; link to set one. |\n| `INVITE_EMAIL_MISMATCH:` / `INVITE_REQUIRES_EMAIL:` | Accepting a team invite with the wrong / no email. | The full message names both addresses — surface it verbatim. |\n\nWhy prefix-coded and not a distinct `error.code`: these are payload/state conditions layered on top of otherwise-valid input, so the HTTP status stays `400` and the stable code lives in the message prefix (the same convention the service uses for `EMAIL_TAKEN`, `USERNAME_TAKEN`). A robust login form does `message.startsWith(\"ACCOUNT_SUSPENDED:\")` rather than switching on the HTTP code.\n\n```ts\n// in your login error handler\ncatch (e) {\n  const msg = e?.message ?? \"\";\n  if (msg.startsWith(\"ACCOUNT_SUSPENDED:\") || msg.startsWith(\"ACCOUNT_DISABLED:\")) {\n    showAccountBlocked(msg);          // not \"invalid credentials\"\n  } else if (msg.startsWith(\"STEP_UP_REQUIRED:\")) {\n    promptForPasswordThenRetrySwitch();\n  } else {\n    showGenericAuthError(msg);\n  }\n}\n```\n\n### Switching tenants\n\nA user who belongs to more than one tenant (e.g. an operator's staff who also has a personal account elsewhere) can mint a fresh session pinned to a different tenant via `POST /v1/identity/auth/switch-tenant` (`{ target_tenant_slug }`, optional `current_password` for step-up). The package does **not** scaffold a tenant-switcher UI — it's an operator-dashboard concern, not a consumer-app login concern. If your app needs it, call the endpoint directly through your `auth-client.ts`; the new token pair comes back in the standard `{ user, tokens }` shape and you store it the same way as a login.\n\n## Server-side handler config\n\nThe `handlerConfig` exported from `lib/auth-config.ts` configures the API catch-all. Most fields default sensibly from env:\n\n```ts\nimport type { HandlerConfig } from \"@bakaburg24/identity-ui/next/handlers\";\n\nexport const handlerConfig: HandlerConfig = {\n  // identityBaseUrl: defaults to process.env.IDENTITY_BASE_URL\n  // tenantSlug:      defaults to process.env.IDENTITY_TENANT_SLUG, then \"mailflix\"\n  // cookieNames:     defaults to { access: \"mf_id_access\", refresh: \"mf_id_refresh\" }\n  // cookieDomain:    unset = host-only cookie. Set to \".example.com\" for cross-subdomain.\n  // secure:          defaults to NODE_ENV === \"production\"\n  // sameSite:        defaults to \"lax\"\n};\n```\n\n## Theming\n\nBrand color flows through inline (no CSS-vars-on-root needed). For deeper theming, override these CSS variables in your stylesheet:\n\n```css\n:root {\n  --siu-bg: #0b0f17;\n  --siu-surface: #11161f;\n  --siu-surface-2: #161c27;\n  --siu-line: rgba(148, 163, 184, 0.16);\n  --siu-line-strong: rgba(148, 163, 184, 0.3);\n  --siu-ink: #e2e8f0;\n  --siu-ink-muted: #94a3b8;\n  --siu-danger: #ef4444;\n  --siu-good: #22c55e;\n  --siu-radius: 8px;\n}\n```\n\nDefaults are dark-mode only today.\n\n## Escape hatches\n\n### Custom page chrome (e.g. tenant picker before the form)\n\nSkip the `/next` re-export. Import `<AuthFlow>` directly and write your own page:\n\n```tsx\n\"use client\";\nimport { AuthFlow, useIdentityUI } from \"@bakaburg24/identity-ui\";\nimport { useRouter, useSearchParams } from \"next/navigation\";\n\nexport default function CustomAuthPage({ params }) {\n  const config = useIdentityUI();\n  const router = useRouter();\n  const search = useSearchParams();\n\n  return (\n    <YourCustomShell>\n      <YourPreFormGate />\n      <AuthFlow\n        segment={...}\n        resetToken={search.get(\"token\") ?? undefined}\n        redirectAfter={search.get(\"next\")}\n        methods={config.methods}\n        client={config.client}\n        brand={config.brand}\n        onAuthenticated={(result, to) => {\n          config.onAuthenticated?.(result);\n          router.push(to ?? \"/\");\n        }}\n      />\n    </YourCustomShell>\n  );\n}\n```\n\n### Per-endpoint API customisation (e.g. extra rate limit on /forgot)\n\nSkip the catch-all. Use the per-endpoint factories:\n\n```ts\nimport {\n  createForgotHandler,\n  resolveConfig,\n} from \"@bakaburg24/identity-ui/next/handlers\";\n\nconst cfg = resolveConfig(handlerConfig);\nexport const POST = withRateLimit(createForgotHandler(cfg), { rps: 1 });\n```\n\n### Individual form components\n\n`LoginForm`, `SignupForm`, `ForgotForm`, `ResetForm`, `MagicLinkForm`, `OauthRow`, `WalletRow` — all exported. Compose any layout you want.\n\n## Backend: the identity service\n\nThis package is the client half. The server half is a Rust + Axum service that lives at [`services/identity/`](../../services/identity/). The authoritative, always-current API surface is the OpenAPI spec the running service serves at `GET /openapi.json` — generate a client from that rather than trusting this list, which is a summary and can lag the service. Major groups:\n\n- Auth: `/v1/identity/auth/email/{signup,login,verify,resend,forgot,reset,change-password}`\n- OAuth: `/v1/identity/auth/oauth/{discord/start,discord/callback,exchange}`\n- Wallet: `/v1/identity/auth/wallet/{nonce,verify}` (Solana sign-message)\n- TOTP / 2FA: setup/enable/disable under `/v1/identity/me/2fa/*`; the mid-login second-factor step is `POST /v1/identity/auth/challenge`\n- Sessions: `/v1/identity/auth/{refresh,logout,switch-tenant}`\n- User profile: `/v1/identity/me/*`\n- Team management (operator dashboard, not this package): `/v1/identity/tenants/:slug/team/*` — invites, members, role changes, ownership transfer\n- End-user admin (operator dashboard, not this package): `/v1/identity/tenants/:slug/admin/users/*` — list, credit, **suspend/disable/reactivate** (`/status`)\n- Multi-tenant ops: `/v1/identity/admin/{tenants,operator-keys}`\n- Operator integration: `/v1/identity/auth/token-exchange` (federated identity — operator backend mints a user+session for its own customer)\n- Public: `/v1/identity/.well-known/jwt-public-key` (RS256 verification key) and `/openapi.json` (full spec)\n\nYou can deploy the identity service to Railway / Fly / your own infra; the package doesn't care about location.\n\n## Multi-tenant model\n\nThe identity service is multi-tenant by design — one Postgres schema, every table has `tenant_id`. Each app (or product, or organisation) is a tenant; tenants share the deployment but have isolated user pools.\n\nReal example: this package is used by mailflix (one tenant, ~6 apps) and solana-shuffle (one tenant, ~2 apps) — two tenants share the same identity service. New tenants come online via the operator-signup flow without a redeploy.\n\n## Versioning\n\nSemver. v0.x means breaking changes can land on minor bumps — pin to an exact version until 1.0.\n\n| Version | Highlights |\n|---|---|\n| `0.1.0` | Initial: components only; consumer wrote a 30-line catch-all page. |\n| `0.2.0` | **Breaking.** Added `IdentityUIProvider` + `/next` page subpath. Consumer becomes 1-line page + 1 provider wrap. |\n| `0.3.0` | **Breaking.** Added `/next/handlers` sub-export + scaffold CLI. Consumer is now 1 init command + 1 config file. |\n| `0.3.1` | Patch. Inlined `RouteContext` type so consumer `next build` doesn't TS4023. |\n| `0.4.0` | **Breaking.** Renamed scope `@mailflix/identity-ui` → `@bakaburg24/identity-ui`. License: MIT. |\n| `0.4.1` | Patch. No public API change (semver-patch; safe in-place bump from `0.4.0`). |\n| `0.5.0` | **Magic-link + Google OAuth now real.** Both were previously UI-only shells with no backend. Now end-to-end: `signInWithMagicLink` + new `consumeMagicLink` client methods, a `magic-link` AuthFlow segment + exported `<MagicLinkConsume>`, `oauth-google` start/exchange handlers. `init` scaffolds working implementations for all three (password, magic-link, oauth-google) — no consumer code. New optional `AuthClient.consumeMagicLink` + `AuthFlowProps.magicLinkToken` (additive; existing consumers unaffected unless they enable `magic-link`). |\n| `0.6.0` | **Email-verification confirm flow.** New `verify` AuthFlow segment + exported `<VerifyConsume>` (auto-submits the token from the `/auth/verify/<token>` email link, shows success, bounces to login — no session, like reset). New optional `AuthClient.verifyEmail` + `resendVerification`, new `AuthFlowProps.verifyToken`. Dispatcher gains `email/verify` + `email/resend` routes; `init` scaffolds both client methods. Backend `verification` email template now links to `/auth/verify/<token>` (path-param, package-router-compatible) instead of the old query-string form. Additive — existing consumers unaffected unless they surface verification. |\n\nIf you're on `@mailflix/identity-ui@0.3.x`, swap to `@bakaburg24/identity-ui@0.4.x`+ — the API is additive otherwise.\n\n> Note: the backend identity service has a few features the package's scaffolded UI still doesn't surface (account suspend/disable, team management, switch-tenant). Those are **service-side** and reachable through the documented endpoints today. The error-handling section above is the one piece a current consumer should act on — a login form built before account-status existed will show \"bad request\" instead of \"your access is suspended\" until it matches the `ACCOUNT_SUSPENDED:` prefix. (As of v0.5.0 magic-link and Google OAuth ARE fully scaffolded — they're no longer in this gap.)\n\n## What's not in here yet\n\nThe identity *service* supports these; the *package* doesn't scaffold UI for them yet. Until it does, wire them through your own `auth-client.ts` against the documented endpoints.\n\n- **TOTP / 2FA challenge UI** (planned). Backend ready: `POST /v1/identity/auth/challenge` is the mid-login second factor; setup/disable live under `/v1/identity/me/2fa/*`.\n- **Email-verification confirm page** (`/auth/verify?token=...`). Backend ready: `POST /v1/identity/auth/email/verify`, and `POST /v1/identity/auth/email/resend` to re-send an expired link. (Note: magic-link sign-in already marks the email verified as a side effect, so a verified email is often achieved without this page.)\n- **Apple OAuth** — the package renders the button, but identity has no Apple provider yet. Don't put `oauth-apple` in `methods` until it ships. (Google IS done as of v0.5.0.)\n- **Sign-out button as a primitive** — most consumers wire their own header.\n- **Tenant-switcher / team-management UI** — operator-dashboard concerns, intentionally out of scope for a consumer auth package (the identity-portal app owns these).\n\n## Development\n\n```bash\ncd packages/identity-ui\npnpm install\npnpm typecheck\npnpm build\n```\n\n## License\n\nMIT — see [LICENSE](./LICENSE). Use it commercially. Modify it. Redistribute it.\n\n## Status\n\nEarly — pre-1.0, breaking changes on minor bumps. Used in production by 8+ apps across two product lines. If you're building something new, the value is real; if you're migrating from Supabase/Clerk, the cost is rewriting your `lib/auth-client.ts` once.\n\nIf you ship something with this package — drop a note, I'd love to see it.\n","readmeFilename":"README.md"}