{"_id":"@ascendenceai/cortena-extensions-web-auth","_rev":"2-74d5a72af01cfaa8560ea85aa6bfe080","name":"@ascendenceai/cortena-extensions-web-auth","dist-tags":{"latest":"0.4.0"},"versions":{"0.1.0":{"name":"@ascendenceai/cortena-extensions-web-auth","version":"0.1.0","license":"SEE LICENSE IN LICENSE","_id":"@ascendenceai/cortena-extensions-web-auth@0.1.0","maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"dist":{"shasum":"5963490332e87e424a424dc466c983724425a193","tarball":"https://registry.npmjs.org/@ascendenceai/cortena-extensions-web-auth/-/cortena-extensions-web-auth-0.1.0.tgz","fileCount":6,"integrity":"sha512-i157bK5+2hRJwqRCnzq5xPI9pQDSV7/XeuLA5YV0gwP2J0C/zOAom7ClONozgl7bS/VgSDroAECDtUqRe+e//g==","signatures":[{"sig":"MEUCIQDunBjpIyZbrccKJ6Pj/8KiWy2lCFzmmD+RFRaiaWNBhgIgGZhRfwgsTutjKq86ZO+a7cZe+iadZsdsiNC2AHO74L4=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":23613},"main":"dist/index.js","type":"module","_from":"file:ascendenceai-cortena-extensions-web-auth-0.1.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"scripts":{"lint":"tsc --noEmit -p tsconfig.test.json","test":"vitest run","build":"tsc","typecheck":"tsc --noEmit -p tsconfig.test.json"},"_npmUser":{"name":"amit_ascendence","email":"connect@mindmentors.net"},"_resolved":"/private/var/folders/yz/wdclk8jx2l3c4bkzs3wg_0z80000gp/T/71c5a8f1c00ecafe193ae36f2892ca58/ascendenceai-cortena-extensions-web-auth-0.1.0.tgz","_integrity":"sha512-i157bK5+2hRJwqRCnzq5xPI9pQDSV7/XeuLA5YV0gwP2J0C/zOAom7ClONozgl7bS/VgSDroAECDtUqRe+e//g==","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/web-auth"},"_npmVersion":"11.9.0","description":"createAuthStore of §6.3: the embedded SPA reads its token from the URL synchronously in getInitialState, before first render, so a ProtectedRoute cannot redirect and strip the query parameters first.","directories":{},"_nodeVersion":"25.6.1","dependencies":{"zustand":"^5.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^25.0.1","react":"^19.0.0","vitest":"^3.0.0","typescript":"^5.7.0","@types/react":"^19.0.0"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"tmp":"tmp/cortena-extensions-web-auth_0.1.0_1788785625952_0.38025013859870493","host":"s3://npm-registry-packages-npm-production"}},"0.4.0":{"_id":"@ascendenceai/cortena-extensions-web-auth@0.4.0","bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"dist":{"shasum":"cdd531b4d60e580837aeca651b31caee5b6d2c6f","tarball":"https://registry.npmjs.org/@ascendenceai/cortena-extensions-web-auth/-/cortena-extensions-web-auth-0.4.0.tgz","fileCount":6,"integrity":"sha512-dROoFq3kwy23nZckPYxRo6ZyUU3uCdI0ivJosLYcAbuKgK8+u+D3KSe4E8s82nNdFZyvcXeFRKIOM3cDhvWwJA==","signatures":[{"sig":"MEYCIQD47HbTDWqWYNxEUmTnYtPXC/Q3+a6fcyA2wfSTWR5SzgIhALKzsnfsGq4PECGlegRaQ/8fWVI9yO32qrnUMNt9NAHN","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIQDBro3cLky8IElfBg7xuBsU5xGPoGqmGiCX0w8WFx0HYAIgUWjgfx/Ye+sgtDiHdkuH3R0IWTn72Y/f83iyZnFOn18="}],"unpackedSize":31397},"main":"dist/index.js","name":"@ascendenceai/cortena-extensions-web-auth","type":"module","_from":"file:ascendenceai-cortena-extensions-web-auth-0.4.0.tgz","types":"dist/index.d.ts","exports":{".":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"./package.json":"./package.json"},"license":"SEE LICENSE IN LICENSE","scripts":{"lint":"tsc --noEmit -p tsconfig.test.json","test":"vitest run","build":"tsc","typecheck":"tsc --noEmit -p tsconfig.test.json"},"version":"0.4.0","_npmUser":{"name":"amit_ascendence","email":"connect@mindmentors.net"},"homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","_resolved":"/private/var/folders/yz/wdclk8jx2l3c4bkzs3wg_0z80000gp/T/b06fcb376b13d24cfc1577e8029b97a1/ascendenceai-cortena-extensions-web-auth-0.4.0.tgz","_integrity":"sha512-dROoFq3kwy23nZckPYxRo6ZyUU3uCdI0ivJosLYcAbuKgK8+u+D3KSe4E8s82nNdFZyvcXeFRKIOM3cDhvWwJA==","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/web-auth"},"_npmVersion":"11.9.0","description":"createAuthStore of §6.3: the embedded SPA reads its token from the URL synchronously in getInitialState, before first render, so a ProtectedRoute cannot redirect and strip the query parameters first.","directories":{},"maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"_nodeVersion":"25.6.1","dependencies":{"zustand":"^5.0.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"jsdom":"^25.0.1","react":"^19.0.0","vitest":"^3.0.0","typescript":"^5.7.0","@types/react":"^19.0.0"},"peerDependencies":{"react":">=18.0.0"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/cortena-extensions-web-auth_0.4.0_1790491390050_0.662450563194181"}}},"time":{"created":"2026-09-07T12:53:45.789Z","modified":"2026-09-27T06:43:10.300Z","0.1.0":"2026-09-07T12:53:46.095Z","0.4.0":"2026-09-27T06:43:10.134Z"},"bugs":{"url":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions/issues"},"license":"SEE LICENSE IN LICENSE","homepage":"https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions#readme","repository":{"url":"git+https://github.com/Ascendence-AI-Technology-Pvt-Ltd/cortena-extensions.git","type":"git","directory":"packages/web-auth"},"description":"createAuthStore of §6.3: the embedded SPA reads its token from the URL synchronously in getInitialState, before first render, so a ProtectedRoute cannot redirect and strip the query parameters first.","maintainers":[{"name":"amit_ascendence","email":"connect@mindmentors.net"}],"readme":"# `@ascendenceai/cortena-extensions-web-auth`\n\nThe embedded SPA's auth store, from §6.3 of *How to create a Cortena extension*.\n\n```ts\n// src/auth/store.ts\nimport { createAuthStore, createAuthProvider, createApiClient } from '@ascendenceai/cortena-extensions-web-auth';\n\nexport const useAuthStore = createAuthStore({ extensionId: 'tasks' });\nexport const AuthProvider = createAuthProvider({ extensionId: 'tasks', useAuthStore, devUsers });\nexport const api = createApiClient({ baseUrl: '/v1', useAuthStore });\n```\n\n## The one bug this exists to make impossible\n\nYour UI runs in a CortenaWeb iframe and is launched with the token in the query\nstring: `?token=…`. Read that token in a `useEffect`, and the first render\nhappens *before* it exists — so `ProtectedRoute` redirects to the login page,\n`replaceState` strips the query parameters, and the token is gone before\nanything could have read it. The symptom is a login page inside the iframe for a\nuser who is already signed in, and it is the single most common embedding\ndefect.\n\n`createAuthStore` reads the URL **synchronously in `getInitialState()`**, before\nfirst render, and only then falls back to `localStorage`. There is no window in\nwhich a route guard can see a signed-out store.\n\nOrder, in `getInitialState`:\n\n1. `?token=` (and `?refreshToken=`) from `window.location.search`, decoded,\n   written to `localStorage`, and the query string cleaned up with\n   `replaceState`.\n2. `localStorage`, under `<extensionId>_token`.\n3. Signed out.\n\n## Exports\n\n| Export | |\n| --- | --- |\n| `createAuthStore(config)` | The zustand store: `token`, `refreshToken`, `user`, `isAuthenticated`, `setTokens`, `logout`, `initialize`. Keys are namespaced by `extensionId`, so two extensions on one origin do not overwrite each other. |\n| `createAuthProvider(config)` | The provider that re-syncs on mount, auto-logs-in as `devUsers[0]` **only** under `import.meta.env.DEV`, and applies the `cortena-theme` message the CortenaWeb parent posts to the iframe. |\n| `createApiClient(config)` | `fetch` with the bearer token attached and a refresh on 401. |\n| `decodeJwt(token)` | The claims a Cortena token carries, or `null`. Decodes; it does not verify — verification is the backend's job (`@ascendenceai/cortena-extensions-auth`). |\n| types | `AuthState`, `CortenaUser`, `OrgRole`, `DummyUser`, `AuthStoreConfig`, `AuthProviderConfig`, `ApiClientConfig`, `ApiClient` |\n\n## Cross-origin storage (§6.4)\n\nBrowsers partition `localStorage` for third-party iframes, so a token stored on\none load may not survive the next. The URL-parameter path works precisely\nbecause the token arrives fresh every time — which is why step 1 above is first\nand why storage failures are swallowed rather than thrown. Keep your host under\n`*.ascendenceai.com`.\n\n## Peer dependencies\n\n`react >= 18`. `zustand` is a real dependency, so the store is one import rather\nthan a setup step.\n","readmeFilename":"README.md"}