@pinooxhq/auth 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,705 @@
1
+ # `@pinooxhq/auth`
2
+
3
+ Auth for Pinoox themes — **configured in PHP, zero hardcoding in the SPA**.
4
+
5
+ Token storage, authorized HTTP, `me()` / logout, and login redirects for **Vue**, **React**, and **Svelte**. Values come from `window.__PINOOX__.auth` (published by `auth.client` in `app.php`).
6
+
7
+ ## Contents
8
+
9
+ 1. [Schematics & how it works](#1-schematics--how-it-works) ← **start here**
10
+ 2. [Setup guide — Independent](#2-setup-guide--independent)
11
+ 3. [Setup guide — SSO](#3-setup-guide--sso)
12
+ 4. [Platform variant](#4-platform-variant)
13
+ 5. [Quick theme module](#5-quick-theme-module)
14
+ 6. [Why `auth.client`?](#6-why-authclient)
15
+ 7. [App configuration reference](#7-app-configuration-reference)
16
+ 8. [Theme integration](#8-theme-integration)
17
+ 9. [HTTP, redirects, adapters](#9-http-redirects-adapters)
18
+ 10. [API reference](#10-api-reference)
19
+ 11. [Vite](#11-vite)
20
+
21
+ ---
22
+
23
+ ## 1. Schematics & how it works
24
+
25
+ Two layers always work together:
26
+
27
+ | Layer | Where | What it controls |
28
+ | ----- | ----- | ---------------- |
29
+ | **`transport`** | `app.php` | Which app owns **users / JWT secret / token key** on the server |
30
+ | **`auth.client`** | `app.php` → `__PINOOX__.auth` | What the **SPA** knows: strategy, login URL, API endpoints |
31
+
32
+ ### Independent mode
33
+
34
+ Each app logs users in by itself. Tokens are **not** shared.
35
+
36
+ ```mermaid
37
+ flowchart LR
38
+ User --> Shop
39
+ User --> CRM
40
+ Shop -.- Sown[own login + token]
41
+ CRM -.- Cown[own login + token]
42
+ ```
43
+
44
+ **How it works**
45
+
46
+ 1. `app.php`: `auth.mode` + `auth.key` + `client => true` (no `transport`).
47
+ 2. Strategy is **`local`** — login UI lives in this app.
48
+ 3. Login issues a JWT stored under that app’s `auth.key`.
49
+ 4. `me` / `logout` hit **this** app’s API only.
50
+
51
+ ---
52
+
53
+ ### SSO mode (one login + dependents)
54
+
55
+ One app (`com_pinoox_account`) is the login. Shop and CRM reuse the **same** session.
56
+
57
+ ```mermaid
58
+ flowchart LR
59
+ Shop -->|no token| Account
60
+ CRM -->|no token| Account
61
+ Account -->|shared token| Shop
62
+ Account -->|shared token| CRM
63
+ ```
64
+
65
+ **How it works**
66
+
67
+ 1. **Account** owns users + `auth.key` (local login).
68
+ 2. **Shop / CRM**: `transport.user => com_pinoox_account` + `strategy: remote`.
69
+ 3. Guest → redirect to Account → login → back with `?redirect=`.
70
+ 4. Same browser key unlocks every dependent app.
71
+
72
+ | | Account | Shop / CRM |
73
+ | - | ------- | ---------- |
74
+ | Login | Yes | No |
75
+ | Token key | Defines it | Shared via `transport` |
76
+ | Strategy | `local` | `remote` |
77
+ ---
78
+
79
+ ## 2. Setup guide — Independent
80
+
81
+ Goal: Shop has its own login. CRM is unrelated (or also independent with a different key).
82
+
83
+ ### Step 1 — `app.php`
84
+
85
+ ```php
86
+ // apps/com_pinoox_shop/app.php
87
+ return [
88
+ 'package' => 'com_pinoox_shop',
89
+ // … theme, router, …
90
+
91
+ // no 'transport' → local users + local auth
92
+
93
+ 'auth' => [
94
+ 'mode' => 'jwt',
95
+ 'key' => 'pinoox_shop_auth',
96
+ 'lifetime' => 30,
97
+ 'lifetime_unit' => 'day',
98
+ 'client' => true,
99
+ ],
100
+ ];
101
+ ```
102
+
103
+ ```bash
104
+ php pinoox pinker:rebuild com_pinoox_shop -n
105
+ ```
106
+
107
+ ### Step 2 — Theme auth module
108
+
109
+ ```bash
110
+ cd apps/com_pinoox_shop/theme/default
111
+ npm install @pinooxhq/auth axios
112
+ ```
113
+
114
+ ```js
115
+ // src/utils/auth/index.js
116
+ import axios from 'axios'
117
+ import { defineStore } from 'pinia'
118
+ import { createAuth, createHttp } from '@pinooxhq/auth'
119
+ import { createPiniaAuthStore } from '@pinooxhq/auth/vue'
120
+
121
+ export const auth = createAuth({ debug: import.meta.env.DEV })
122
+ export const http = createHttp({ auth, axios })
123
+ export const useAuthStore = createPiniaAuthStore(defineStore, 'auth')
124
+ ```
125
+
126
+ ### Step 3 — Login page
127
+
128
+ ```vue
129
+ <script setup>
130
+ import { http, useAuthStore } from '@/utils/auth'
131
+ import { useAuthRedirect } from '@pinooxhq/auth/vue'
132
+
133
+ const store = useAuthStore()
134
+ const { redirectBack } = useAuthRedirect()
135
+
136
+ const onLogin = async () => {
137
+ const { data } = await http.post('auth/login', { username, password })
138
+ const body = data?.data ?? data
139
+ store.login(body.token, body.user)
140
+ redirectBack()
141
+ }
142
+ </script>
143
+ ```
144
+
145
+ ### Step 4 — Guard
146
+
147
+ ```js
148
+ import { useAuthStore } from '@/utils/auth'
149
+
150
+ export async function authGuard(to, _from, next) {
151
+ const store = useAuthStore()
152
+ store.syncTokenFromStorage()
153
+ if (!store.isAuth) await store.canUserAccess(true)
154
+ if (to.meta.requiresAuth && !store.isAuth) {
155
+ return next({ name: 'login', query: { redirect: to.fullPath } })
156
+ }
157
+ next()
158
+ }
159
+ ```
160
+
161
+ ### Checklist
162
+
163
+ - [ ] `auth.mode` + `auth.key` on this app
164
+ - [ ] `auth.client => true`
165
+ - [ ] Login UI in **this** theme
166
+ - [ ] Do **not** set `strategy: remote`
167
+ - [ ] Twig: `pinoox_bootstrap` before `vite_tags`
168
+
169
+ ---
170
+
171
+ ## 3. Setup guide — SSO
172
+
173
+ Goal: Account is the only login. Shop and CRM reuse one session.
174
+
175
+ ### Step 1 — Provider (`com_pinoox_account`)
176
+
177
+ ```php
178
+ // apps/com_pinoox_account/app.php
179
+ 'auth' => [
180
+ 'mode' => 'jwt',
181
+ 'key' => 'pinoox_ecosystem', // shared by every dependent
182
+ 'client' => true, // local strategy
183
+ ],
184
+ ```
185
+
186
+ Account theme: normal **local** login (same as Independent Step 3), then `redirectBack()` so `?redirect=` returns the user to Shop/CRM.
187
+
188
+ ```bash
189
+ php pinoox pinker:rebuild com_pinoox_account -n
190
+ ```
191
+
192
+ ### Step 2 — Dependent Shop
193
+
194
+ ```php
195
+ // apps/com_pinoox_shop/app.php
196
+ 'depends' => ['com_pinoox_account'],
197
+
198
+ 'transport' => [
199
+ 'user' => 'com_pinoox_account', // inherit users + JWT secret + key
200
+ ],
201
+
202
+ 'auth' => [
203
+ 'client' => [
204
+ 'strategy' => 'remote',
205
+ 'loginUrl' => '/account/login',
206
+ 'baseUrl' => '/account/api/v1',
207
+ 'endpoints' => [
208
+ 'login' => 'auth/login',
209
+ 'logout' => 'auth/logout',
210
+ 'me' => 'auth/get',
211
+ ],
212
+ ],
213
+ ],
214
+ ```
215
+
216
+ ### Step 3 — Dependent CRM (same pattern)
217
+
218
+ ```php
219
+ // apps/com_pinoox_crm/app.php
220
+ 'depends' => ['com_pinoox_account'],
221
+ 'transport' => ['user' => 'com_pinoox_account'],
222
+ 'auth' => [
223
+ 'client' => [
224
+ 'strategy' => 'remote',
225
+ 'loginUrl' => '/account/login',
226
+ 'baseUrl' => '/account/api/v1',
227
+ 'endpoints' => [
228
+ 'me' => 'auth/get',
229
+ 'logout' => 'auth/logout',
230
+ ],
231
+ ],
232
+ ],
233
+ ```
234
+
235
+ ```bash
236
+ php pinoox pinker:rebuild com_pinoox_shop -n
237
+ php pinoox pinker:rebuild com_pinoox_crm -n
238
+ ```
239
+
240
+ ### Step 4 — Theme on each dependent
241
+
242
+ Same `utils/auth` as Independent — **no hardcoded key**. `createAuth()` reads remote settings from `__PINOOX__.auth`.
243
+
244
+ ```js
245
+ export const auth = createAuth({ debug: import.meta.env.DEV })
246
+ export const http = createHttp({ auth, axios })
247
+ export const useAuthStore = createPiniaAuthStore(defineStore, 'auth')
248
+ ```
249
+
250
+ Guard — redirect to Account when guest:
251
+
252
+ ```js
253
+ import { auth, useAuthStore } from '@/utils/auth'
254
+
255
+ export async function authGuard(to, _from, next) {
256
+ const store = useAuthStore()
257
+ store.syncTokenFromStorage()
258
+ if (!store.isAuth) await store.canUserAccess(true)
259
+ if (to.meta.requiresAuth && !store.isAuth) {
260
+ auth.redirectToLogin() // → /account/login?redirect=…
261
+ return
262
+ }
263
+ next()
264
+ }
265
+ ```
266
+
267
+ Do **not** build a login form on Shop/CRM for SSO — only Account has the form.
268
+
269
+ ### Checklist (dependent)
270
+
271
+ - [ ] `transport.user` → `com_pinoox_account`
272
+ - [ ] `auth.client.strategy` = `remote`
273
+ - [ ] `loginUrl` = `/account/login`
274
+ - [ ] `baseUrl` + `endpoints` for `me` / `logout`
275
+ - [ ] Do **not** set a different `auth.key` (unless you want a separate session)
276
+ - [ ] Pinker rebuild after `app.php` changes
277
+
278
+ ### Side-by-side
279
+
280
+ | | Independent | SSO |
281
+ | - | ----------- | --- |
282
+ | Example | Shop alone; CRM alone | Account ← Shop, CRM |
283
+ | `transport.user` | — | `com_pinoox_account` |
284
+ | Login UI | Each app | Account only |
285
+ | Browser key | Per app (`pinoox_shop_auth`, …) | One key (`pinoox_ecosystem`) |
286
+ | `auth.client` | `true` | map with `strategy: remote` |
287
+
288
+ ---
289
+
290
+ ## 4. Platform variant
291
+
292
+ Same idea as SSO, but the login owner is the **platform** auth app (Manager), not Account. Use for admin tools that must not share the customer Account session.
293
+
294
+ ```mermaid
295
+ flowchart LR
296
+ CRM -->|no token| Manager
297
+ Inventory -->|no token| Manager
298
+ Manager -->|one token| CRM
299
+ Manager -->|one token| Inventory
300
+ ```
301
+
302
+ ```php
303
+ // dependent admin app
304
+ 'transport' => ['user' => 'platform'],
305
+ 'auth' => [
306
+ 'client' => [
307
+ 'strategy' => 'remote',
308
+ 'loginUrl' => '/manager/login',
309
+ 'baseUrl' => '/crm/api/v1',
310
+ 'endpoints' => [
311
+ 'me' => 'auth/get',
312
+ 'logout' => 'auth/logout',
313
+ ],
314
+ ],
315
+ ],
316
+ ```
317
+
318
+ | `transport.user` | Auth / users come from |
319
+ | ---------------- | ---------------------- |
320
+ | omitted / `local` | This app only (independent) |
321
+ | `'com_pinoox_account'` | Account SSO |
322
+ | `'platform'` | Platform / Manager SSO |
323
+
324
+ ---
325
+
326
+ ## 5. Quick theme module
327
+
328
+ Same for all topologies — only `__PINOOX__.auth` changes.
329
+
330
+ ```bash
331
+ cd apps/com_pinoox_shop/theme/default
332
+ npm install @pinooxhq/auth axios
333
+ php pinoox pinker:rebuild com_pinoox_shop -n
334
+ ```
335
+
336
+ `src/utils/auth/index.js`:
337
+
338
+ ```js
339
+ import axios from 'axios'
340
+ import { defineStore } from 'pinia'
341
+ import { createAuth, createHttp } from '@pinooxhq/auth'
342
+ import { createPiniaAuthStore } from '@pinooxhq/auth/vue'
343
+
344
+ export const auth = createAuth({ debug: import.meta.env.DEV })
345
+ export const http = createHttp({ auth, axios })
346
+ export const useAuthStore = createPiniaAuthStore(defineStore, 'auth')
347
+ ```
348
+
349
+ ```js
350
+ import { auth, useAuthStore } from '@/utils/auth'
351
+
352
+ const store = useAuthStore()
353
+ await store.canUserAccess(true)
354
+
355
+ if (!store.isAuth) {
356
+ auth.redirectToLogin() // remote → loginUrl; local → your login route
357
+ }
358
+ ```
359
+
360
+ ---
361
+
362
+ ## 6. Why `auth.client`?
363
+
364
+ PHP already owns `mode`, `key`, JWT secret, and login URLs. The SPA must not invent them.
365
+
366
+ `auth.client` publishes a **safe subset** to `window.__PINOOX__.auth` (via `pinoox_bootstrap()`).
367
+
368
+ | Stays on server | May go to the client |
369
+ | --------------- | -------------------- |
370
+ | `jwt_secret`, lifetimes, … | `mode`, `key`, `provider`, `source` |
371
+ | | optional: `strategy`, `loginUrl`, `baseUrl`, `endpoints` |
372
+
373
+ ```
374
+ app.php auth.client → pinker + pinoox_bootstrap() → __PINOOX__.auth
375
+
376
+ createAuth() / createHttp()
377
+ ```
378
+
379
+ Twig (order matters):
380
+
381
+ ```twig
382
+ {{ pinoox_bootstrap(bootstrap|default({}))|raw }}
383
+ {{ vite_tags() }}
384
+ ```
385
+
386
+ ### `auth.client` — what gets into `__PINOOX__.auth`
387
+
388
+ The server always resolves a **base** payload from auth + transport:
389
+
390
+ ```php
391
+ // Always available on the server (AuthConfig::forClient base)
392
+ [
393
+ 'mode' => 'jwt', // jwt | cookie | session
394
+ 'key' => 'pinoox_ecosystem', // localStorage / cookie name
395
+ 'provider' => 'com_pinoox_account', // package that owns auth (or this app)
396
+ 'source' => 'com_pinoox_account', // transport auth source (or null)
397
+ ]
398
+ ```
399
+
400
+ `auth.client` decides **how much of that (and extras) the browser may see**.
401
+
402
+ #### 1) `true` — publish the full base (default)
403
+
404
+ ```php
405
+ // e.g. com_pinoox_shop — independent login
406
+ 'auth' => [
407
+ 'mode' => 'jwt',
408
+ 'key' => 'pinoox_shop_auth',
409
+ 'client' => true,
410
+ ],
411
+ ```
412
+
413
+ ```js
414
+ // window.__PINOOX__.auth
415
+ { mode: 'jwt', key: 'pinoox_shop_auth', provider: 'com_pinoox_shop', source: null }
416
+ ```
417
+
418
+ Use for a normal local login app. `createAuth()` gets mode + key with no JS hardcoding.
419
+
420
+ #### 2) `false` — publish nothing
421
+
422
+ ```php
423
+ 'auth' => [
424
+ 'mode' => 'jwt',
425
+ 'key' => 'pinoox_crm_auth',
426
+ 'client' => false,
427
+ ],
428
+ ```
429
+
430
+ `__PINOOX__.auth` is omitted. You must pass everything in JS:
431
+
432
+ ```js
433
+ createAuth({ mode: 'jwt', key: 'pinoox_crm_auth', strategy: 'local' })
434
+ ```
435
+
436
+ Use only when you deliberately keep auth config off the page (or hydrate from elsewhere).
437
+
438
+ #### 3) List — whitelist of base fields only
439
+
440
+ A **PHP list** (`array_is_list`) means: from the base payload, keep **only** these keys. Nothing else is added.
441
+
442
+ ```php
443
+ // Account (or Shop) — only mode + key in the page; hide provider/source package names
444
+ 'auth' => [
445
+ 'mode' => 'jwt',
446
+ 'key' => 'pinoox_ecosystem',
447
+ 'client' => ['mode', 'key'],
448
+ ],
449
+ ```
450
+
451
+ ```js
452
+ // window.__PINOOX__.auth
453
+ { mode: 'jwt', key: 'pinoox_ecosystem' }
454
+ // no provider, no source
455
+ ```
456
+
457
+ **When to use**
458
+
459
+ - The SPA only needs `mode` + `key` to store/send the token
460
+ - You do **not** want `provider` / `source` package ids visible in page HTML
461
+ - Strategy / login URLs are either defaults (`local`) or set in JS / via a **map** (form 4)
462
+
463
+ **Allowed whitelist names** (only these exist on the base):
464
+
465
+ | Field | Purpose |
466
+ | ----- | ------- |
467
+ | `mode` | How the token is stored / sent |
468
+ | `key` | Storage key (`localStorage` name) |
469
+ | `provider` | Auth-owning package |
470
+ | `source` | Transport auth source package |
471
+
472
+ Unknown names are ignored. You **cannot** whitelist `strategy` / `loginUrl` here — those are not base fields; use a **map** (below).
473
+
474
+ Another example — token key only (mode defaults to `jwt` in `createAuth` if missing, but publishing mode is safer):
475
+
476
+ ```php
477
+ 'client' => ['key'],
478
+ // → __PINOOX__.auth = { key: 'pinoox_ecosystem' }
479
+ ```
480
+
481
+ #### 4) Map — base + extras (remote apps)
482
+
483
+ An **associative array** merges onto the full base. Use this for `strategy`, `loginUrl`, `baseUrl`, `endpoints`:
484
+
485
+ ```php
486
+ // com_pinoox_shop — depends on Account
487
+ 'auth' => [
488
+ 'client' => [
489
+ 'strategy' => 'remote',
490
+ 'loginUrl' => '/account/login',
491
+ 'baseUrl' => '/account/api/v1',
492
+ 'endpoints' => [
493
+ 'me' => 'auth/get',
494
+ 'logout' => 'auth/logout',
495
+ ],
496
+ ],
497
+ ],
498
+ ```
499
+
500
+ ```js
501
+ // window.__PINOOX__.auth
502
+ {
503
+ mode: 'jwt',
504
+ key: 'pinoox_ecosystem', // inherited via transport from Account
505
+ provider: '…',
506
+ source: '…',
507
+ strategy: 'remote',
508
+ loginUrl: '/account/login',
509
+ baseUrl: '/account/api/v1',
510
+ endpoints: { me: 'auth/get', logout: 'auth/logout' },
511
+ }
512
+ ```
513
+
514
+ **List vs map (easy rule)**
515
+
516
+ | Shape | PHP | Meaning |
517
+ | ----- | --- | ------- |
518
+ | List | `['mode', 'key']` | Filter base → only these keys |
519
+ | Map | `['strategy' => 'remote', …]` | Keep **all** base keys, then merge extras |
520
+
521
+ ```php
522
+ // ❌ Wrong — this is a list, so "strategy" is ignored (not a base field)
523
+ 'client' => ['mode', 'key', 'strategy'],
524
+
525
+ // ✅ Right — map merges strategy onto the full base
526
+ 'client' => [
527
+ 'strategy' => 'remote',
528
+ 'loginUrl' => '/account/login',
529
+ ],
530
+ ```
531
+
532
+ Aliases for `auth.client`: `via`, `expose`, `bootstrap`.
533
+
534
+ ---
535
+
536
+ ## 7. App configuration reference
537
+
538
+ ### Strategies
539
+
540
+ | Value | Meaning |
541
+ | ----- | ------- |
542
+ | `local` (default) | This app owns login |
543
+ | `remote` | Redirect to another app’s login |
544
+
545
+ Aliases: `provider` → `local`, `consumer` / `external` → `remote`.
546
+
547
+ ### `baseUrl` + endpoints
548
+
549
+ Prefer a shared API prefix (here: Account as provider):
550
+
551
+ ```php
552
+ 'baseUrl' => '/account/api/v1',
553
+ 'endpoints' => [
554
+ 'me' => 'auth/get', // → /account/api/v1/auth/get
555
+ 'logout' => 'auth/logout',
556
+ ],
557
+ ```
558
+
559
+ Or absolute paths (no `baseUrl`):
560
+
561
+ ```php
562
+ 'endpoints' => [
563
+ 'me' => '/account/api/v1/auth/get',
564
+ 'logout' => '/account/api/v1/auth/logout',
565
+ ],
566
+ ```
567
+
568
+ For a dependent that validates the session on **its own** API (same shared key):
569
+
570
+ ```php
571
+ // com_pinoox_crm with transport.user => platform or account
572
+ 'baseUrl' => '/crm/api/v1',
573
+ 'endpoints' => [
574
+ 'me' => 'auth/get',
575
+ 'logout' => 'auth/logout',
576
+ ],
577
+ ```
578
+
579
+ Rules:
580
+
581
+ - `https://…` or path starting with `/` → used as-is
582
+ - relative + `baseUrl` → joined
583
+ - relative without `baseUrl` → left as-is
584
+
585
+ ### Modes
586
+
587
+ | mode | Storage | Request |
588
+ | ---- | ------- | ------- |
589
+ | `jwt` | token under `auth.key` | `Authorization: Bearer …` |
590
+ | `cookie` / `session` | optional | cookies (`credentials: include`) |
591
+
592
+ ---
593
+
594
+ ## 8. Theme integration
595
+
596
+ ### Login page (local / provider only)
597
+
598
+ ```vue
599
+ <script setup>
600
+ import { http, useAuthStore } from '@/utils/auth'
601
+ import { useAuthRedirect } from '@pinooxhq/auth/vue'
602
+
603
+ const store = useAuthStore()
604
+ const { redirectBack } = useAuthRedirect()
605
+
606
+ const onLogin = async () => {
607
+ const { data } = await http.post('auth/login', { username, password })
608
+ const body = data?.data ?? data
609
+ store.login(body.token, body.user)
610
+ redirectBack()
611
+ }
612
+ </script>
613
+ ```
614
+
615
+ ### Router guard (any app)
616
+
617
+ ```js
618
+ import { auth, useAuthStore } from '@/utils/auth'
619
+
620
+ export async function authGuard(to, _from, next) {
621
+ const store = useAuthStore()
622
+ store.syncTokenFromStorage()
623
+
624
+ if (!store.isAuth) {
625
+ await store.canUserAccess(true)
626
+ }
627
+
628
+ if (to.meta.requiresAuth && !store.isAuth) {
629
+ auth.redirectToLogin()
630
+ return
631
+ }
632
+
633
+ next()
634
+ }
635
+ ```
636
+
637
+ On remote apps (Shop / CRM), `auth.redirectToLogin()` goes to `loginUrl?redirect=<current path>`.
638
+
639
+ ---
640
+
641
+ ## 9. HTTP, redirects, adapters
642
+
643
+ ### HTTP
644
+
645
+ `createHttp({ auth, axios })` wires:
646
+
647
+ - `Authorization` from `auth.getAuthHeader()`
648
+ - `401` → `auth.notifyUnauthorized()`
649
+ - shared transport for `login` / `me` / `logout`
650
+
651
+ `baseURL` defaults to `__PINOOX__.url.API`.
652
+
653
+ ### Redirects
654
+
655
+ ```js
656
+ auth.redirectToLogin() // remote → other app; local → loginUrl / endpoints.login
657
+ auth.redirectBack() // after login → safe ?redirect= (SITE origin aware for Vite HMR)
658
+ ```
659
+
660
+ ```js
661
+ import { useAuthRedirect } from '@pinooxhq/auth/vue'
662
+ const { redirectQuery, redirectBack, returnUrl } = useAuthRedirect()
663
+ ```
664
+
665
+ ### Vue / React / Svelte
666
+
667
+ ```js
668
+ import { useAuth, useAuthRedirect, createPiniaAuthStore } from '@pinooxhq/auth/vue'
669
+ import { AuthProvider, useAuth } from '@pinooxhq/auth/react'
670
+ import { createAuthStore, createAuthRedirect } from '@pinooxhq/auth/svelte'
671
+ ```
672
+
673
+ ---
674
+
675
+ ## 10. API reference
676
+
677
+ ```js
678
+ auth.login({ username, password }) // local only; remote → redirect
679
+ auth.me()
680
+ auth.logout()
681
+ auth.getToken() / auth.setToken(t) / auth.clearToken()
682
+ auth.getAuthHeader()
683
+ auth.redirectToLogin() / auth.redirectBack()
684
+ auth.getReturnUrl() / auth.getRedirectQuery()
685
+ auth.notifyUnauthorized()
686
+
687
+ http.get / http.post / …
688
+ ```
689
+
690
+ ---
691
+
692
+ ## 11. Vite
693
+
694
+ [`@pinooxhq/vite-plugin`](https://www.npmjs.com/package/@pinooxhq/vite-plugin) — Twig shell + Vite entry so `__PINOOX__` (and `auth.client`) loads before the SPA.
695
+
696
+ ```twig
697
+ {{ pinoox_bootstrap(bootstrap|default({}))|raw }}
698
+ {{ vite_tags() }}
699
+ ```
700
+
701
+ ---
702
+
703
+ ## License
704
+
705
+ MIT