@jskit-ai/agent-docs 0.1.62 → 0.1.64

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.
@@ -2,9 +2,9 @@
2
2
 
3
3
  # Authentication
4
4
 
5
- At the end of the previous chapter, the app had a real shell, but it still did not know how to sign users in. In this chapter, we connect the app to Supabase Auth, add the stock JSKIT login and sign-out routes, and inspect the files that make authentication visible in the shell.
5
+ At the end of the previous chapter, the app had a real shell, but it still did not know how to sign users in. In this chapter, we add JSKIT's local auth provider, the stock login and sign-out routes, and the files that make authentication visible in the shell.
6
6
 
7
- This chapter still does **not** add the database-backed users layer. That is intentional. It lets us see the authentication pieces clearly before account pages, profile storage, and workspace membership are added later.
7
+ This chapter still does **not** add Supabase or the database-backed users layer. That is intentional. Keep authentication basic until the app's main product workflows exist, then add Supabase, OAuth, OTP, provider linking, profile projection, or workspace/account complexity only when those features are actually needed.
8
8
 
9
9
  To get back to the same starting point as the end of the previous chapter, run:
10
10
 
@@ -16,56 +16,23 @@ npm install
16
16
 
17
17
  If you are already continuing from the previous chapter, you are already in the right place and can skip that setup.
18
18
 
19
- **Supabase: Create The Project First**
19
+ **Default: Start With Local Auth**
20
20
 
21
- Before installing the auth packages, create a Supabase project and collect the three values JSKIT needs.
21
+ The default auth path does not require a Supabase project or a database. It uses the local provider package, `auth-provider-local-core`, together with `auth-web`. The local provider stores credentials and sessions in `.jskit/auth/` by default.
22
22
 
23
- 1. In the Supabase dashboard, create a new project in your organization and wait for it to finish provisioning.
24
- 2. Open `Project Settings -> API`. Copy:
25
- - the **Project URL**
26
- - the **publishable** key
27
- 3. Open `Authentication -> URL Configuration`.
28
- 4. Set **Site URL** to:
29
-
30
- ```text
31
- http://localhost:5173
32
- ```
33
-
34
- 5. Add this local redirect URL:
35
-
36
- ```text
37
- http://localhost:5173/auth/login
38
- ```
39
-
40
- JSKIT uses the project URL as `AUTH_SUPABASE_URL`, the publishable key as `AUTH_SUPABASE_PUBLISHABLE_KEY`, and the browser address as `APP_PUBLIC_URL`.
41
-
42
- Use the **publishable** key here, not the secret key and not a service-role key. If you later run the app on a different host or port, update both Supabase's URL settings and `APP_PUBLIC_URL` to match the real browser URL exactly.
43
-
44
- If you are guiding someone interactively, ask plainly for these exact values:
45
-
46
- - `AUTH_SUPABASE_URL`
47
- - `AUTH_SUPABASE_PUBLISHABLE_KEY`
48
- - whether `APP_PUBLIC_URL` should stay `http://localhost:5173`
49
-
50
- Do not hide behind vague language like "send the auth credentials later." In this local guide flow, those are routine setup values for the Supabase auth install step.
23
+ That is the recommended first step for most apps. After the app's core flow is working, switch to Supabase or add richer auth methods only if the product needs them.
51
24
 
52
25
  ## Installing the auth layer
53
26
 
54
27
  From inside `exampleapp`, run:
55
28
 
56
29
  ```bash
57
- npx jskit add package auth-provider-supabase-core \
58
- --auth-supabase-url "https://YOUR-PROJECT.supabase.co" \
59
- --auth-supabase-publishable-key "sb_publishable_..." \
60
- --app-public-url "http://localhost:5173"
61
-
62
- npx jskit add bundle auth-base
30
+ npx jskit add package auth-provider-local-core
31
+ npx jskit add package auth-web
63
32
  npm install
64
33
  ```
65
34
 
66
- The first command is the provider step. It tells JSKIT which authentication backend to use and writes the project-specific values into the app.
67
-
68
- The second command adds the web auth layer. `auth-base` is a small bundle, not a black box: in practice it adds `auth-core` and `auth-web`, which together provide the auth routes, the auth surface, and the stock login and sign-out screens.
35
+ These package installs add the provider-neutral auth core, the auth web layer, and the local provider. They give the app working register, login, logout, session, and password recovery flows without requiring an external auth service.
69
36
 
70
37
  The final `npm install` matters for the same reason it did in the shell chapter: `jskit add` rewrites the scaffold and updates `package.json`, but `npm install` is what actually downloads the newly referenced runtime packages.
71
38
 
@@ -80,12 +47,11 @@ npm run server
80
47
 
81
48
  Then open `http://localhost:5173/auth/login` in the browser.
82
49
 
83
- The login page is real, and it already contains several different auth modes behind the same card.
50
+ The login page is real and renders only the modes supported by the active provider.
84
51
 
85
52
  - **Sign in** is the normal email-and-password flow.
86
- - **Register** creates a new Supabase auth user.
87
- - **Forgot password?** requests a password reset email.
88
- - **Use one-time code** switches to an email OTP login flow.
53
+ - **Register** creates a local auth user.
54
+ - **Forgot password?** requests a password reset link when recovery is configured.
89
55
  - **Remember this account on this device** stores a small local hint in browser storage so the next visit can greet the last-used account and let the user keep that email preselected.
90
56
 
91
57
  If you go back to `http://localhost:5173/home`, the shell also has a small auth widget in the status area. When you are signed out it shows a guest state and a menu entry that leads to `/auth/login`. After you sign in, the same placement changes to a sign-out menu.
@@ -107,7 +73,7 @@ This is the first screen you see.
107
73
  The small links under the password field are not decoration.
108
74
 
109
75
  - `Forgot password?` switches the card into password-reset-request mode.
110
- - `Use one-time code` switches the card into OTP mode.
76
+ - `Use one-time code` switches the card into OTP mode when the active provider supports OTP. The local provider does not show this link.
111
77
 
112
78
  ### Register mode
113
79
 
@@ -117,16 +83,16 @@ When the user presses `Register`, the form changes in three important ways.
117
83
  - A `Confirm password` field appears.
118
84
  - The main submit button changes from `Sign in` to `Register`.
119
85
 
120
- Pressing `Register` sends a sign-up request to Supabase through `POST /api/register`. JSKIT also sends a starter `display_name` value derived from the email prefix, so a user registering as `alice@example.com` starts with a display name like `alice`.
86
+ Pressing `Register` sends a sign-up request through `POST /api/register`. With the local provider, JSKIT writes the local auth record and creates a session immediately.
121
87
 
122
- What happens next depends on Supabase's email-confirmation settings.
88
+ What happens next depends on the active provider's capabilities.
123
89
 
124
- - If email confirmation is required, Supabase creates the user immediately but does not return a live session. The card then switches to a dedicated confirmation state.
90
+ - If email confirmation is required, the provider creates the user but does not return a live session. The card then switches to a dedicated confirmation state.
125
91
  - If email confirmation is not required, the user is registered and signed in immediately.
126
92
 
127
93
  ### Email-confirmation state
128
94
 
129
- If Supabase requires confirmation, the screen changes again.
95
+ If the active provider requires confirmation, the screen changes again. The local provider does not require email confirmation, so most first apps will not see this mode.
130
96
 
131
97
  - The mode-switch buttons disappear.
132
98
  - The card shows a confirmation message instead of the email/password fields.
@@ -134,11 +100,11 @@ If Supabase requires confirmation, the screen changes again.
134
100
  - `Resend confirmation email` calls `POST /api/register/confirmation/resend`.
135
101
  - `Back to sign in` returns to the normal login mode.
136
102
 
137
- This matters because a junior developer might otherwise assume registration failed. In reality, the user usually already exists in Supabase at this point; they just do not have an active session yet.
103
+ This matters because a junior developer might otherwise assume registration failed. In reality, the provider may already have created the auth identity; the browser just does not have an active session yet.
138
104
 
139
105
  ### One-time-code mode
140
106
 
141
- `Use one-time code` changes the form again.
107
+ When a provider supports OTP, `Use one-time code` changes the form again.
142
108
 
143
109
  - The password field disappears.
144
110
  - A `One-time code` field appears.
@@ -150,7 +116,7 @@ Those two buttons do different jobs.
150
116
  - `Send one-time code` requests the email through `POST /api/login/otp/request`.
151
117
  - `Verify code` submits the code through `POST /api/login/otp/verify`.
152
118
 
153
- JSKIT asks Supabase to send OTP login emails only for existing users. In other words, this flow is for signing in, not for silently creating a new account.
119
+ The local provider does not implement OTP. Supabase and future providers can expose this mode through the same capability contract. In other words, OTP is an opt-in sign-in method, not something the first app needs up front.
154
120
 
155
121
  ### Password-reset-request mode
156
122
 
@@ -160,9 +126,9 @@ JSKIT asks Supabase to send OTP login emails only for existing users. In other w
160
126
  - The main button changes to `Send reset instructions`.
161
127
  - Submitting this screen calls `POST /api/password/forgot`.
162
128
 
163
- That endpoint asks Supabase to send a password reset email. In this chapter's scaffold, this mode is only the **request** step.
129
+ That endpoint asks the active provider to start password recovery. With the local provider, SMTP is used when configured; otherwise local development can log or return the recovery URL depending on environment settings.
164
130
 
165
- The backend already supports the later recovery endpoints too, but the chapter's simple app does **not** yet scaffold a dedicated app-owned `reset-password` page. So at this stage, the guide should be read as: the screen can request recovery emails, while the full browser-side password-reset completion UI is still something you would add explicitly in the app.
131
+ The recovery link opens `/auth/reset-password`. That page exchanges the recovery token for a short-lived recovery-scoped session, lets the user choose a new password, and then clears the recovery session.
166
132
 
167
133
  ### Remembered account behavior
168
134
 
@@ -179,22 +145,22 @@ On the next visit, the card can show a `Welcome back, ...` panel and a `Use anot
179
145
 
180
146
  ### OAuth buttons
181
147
 
182
- The screen is also ready to show OAuth provider buttons, but only if providers are configured.
148
+ The screen is also ready to show OAuth provider buttons, but only if the active provider supports OAuth and providers are configured. Local auth intentionally keeps this off.
183
149
 
184
- For this chapter, `config.server.js` keeps this empty:
150
+ For a later Supabase setup, `config.server.js` can keep this empty at first:
185
151
 
186
152
  ```js
187
153
  config.auth ||= {};
188
- config.auth.profileMode = "standalone";
154
+ config.auth.profileMode = "provider";
189
155
  config.auth.oauth = {
190
156
  providers: [],
191
157
  defaultProvider: ""
192
158
  };
193
159
  ```
194
160
 
195
- That is why the login card in this chapter does not show buttons like `Continue with Google`.
161
+ That is why the login card does not show buttons like `Continue with Google`.
196
162
 
197
- To turn on Google later, there are two separate setup steps.
163
+ After switching to a provider that supports OAuth, turning on Google has two separate setup steps.
198
164
 
199
165
  First, configure Google and Supabase:
200
166
 
@@ -219,67 +185,53 @@ config.auth.oauth = {
219
185
 
220
186
  **Important: What Works Without A Database**
221
187
 
222
- Authentication is already real in this chapter because Supabase is still the source of truth for the important auth data:
188
+ Authentication is already real in this chapter because the local provider owns the auth data it needs:
223
189
 
224
- - the real auth user record
225
- - the password hash and password-reset state
226
- - the OTP and OAuth flows
227
- - the access and refresh tokens that JSKIT writes into cookies
190
+ - local auth user records
191
+ - password hashes
192
+ - recovery tokens
193
+ - access and refresh sessions
194
+ - the HTTP-only cookies JSKIT writes for the browser
228
195
 
229
- What is missing is JSKIT's own database-backed users layer. In no-database mode, the auth provider switches to **standalone in-memory fallbacks** for the app-side data it normally mirrors into JSKIT tables.
196
+ What is missing is JSKIT's own database-backed users layer. That is intentional for the first app. Login should not require MySQL, Postgres, Supabase, or a generated users table before the product itself exists.
230
197
 
231
198
  Concretely, that means:
232
199
 
233
- - JSKIT still creates a local profile mirror for each authenticated Supabase user.
234
- - But that mirror lives only in the Node process, not in a database table.
235
- - That temporary mirror stores only a small app-side profile shape:
236
- - `id`
237
- - `authProvider`
238
- - `authProviderUserSid`
239
- - `email`
240
- - `displayName`
241
- - JSKIT also keeps a tiny in-memory user-settings record for auth-related flags such as:
242
- - `passwordSignInEnabled`
243
- - `passwordSetupRequired`
200
+ - JSKIT can register a user.
201
+ - JSKIT can sign that user in and out.
202
+ - JSKIT can read the current session.
203
+ - JSKIT can request and complete password recovery.
204
+ - JSKIT can run without any JSKIT database runtime package.
244
205
 
245
- So the behavior is:
206
+ By default, the local provider stores its runtime state under `.jskit/auth/`:
246
207
 
247
- - register a user -> the real user is created in Supabase
248
- - sign in -> Supabase still verifies credentials and returns the real session
249
- - JSKIT then mirrors just enough profile data into memory so the app can work
250
- - restart the local server -> that JSKIT-side mirror and those fallback settings are cleared
208
+ - `users.passwd`
209
+ - `sessions.passwd`
210
+ - `recovery.passwd`
211
+ - `session.secret`
251
212
 
252
- The browser session is a different thing. In the normal case, a server restart does **not** log the browser out. The browser still has the auth cookies, so on the next request JSKIT can read those cookies, validate or refresh the Supabase session, and rebuild the temporary mirror.
253
-
254
- That last point is the important difference. A restart does **not** delete the Supabase user. It does **not** erase the real password. It does **not** erase the real auth session in Supabase itself. What it clears is only the app's temporary in-memory mirror. On the next authenticated request, JSKIT rebuilds that mirror from the Supabase user or token claims.
213
+ Those files are local runtime state, not generated application code. They are ignored by git.
255
214
 
256
215
  So without a database, you still get:
257
216
 
258
217
  - real login
259
218
  - real logout
260
219
  - real registration
261
- - real password reset requests
262
- - real OTP and OAuth flows
220
+ - real password reset requests and completions
263
221
  - real session cookies
264
222
 
265
223
  But you do **not** get:
266
224
 
267
225
  - persistent JSKIT-side user rows
268
- - persistent JSKIT-side user settings
269
- - account/profile persistence beyond what Supabase itself stores
226
+ - persistent JSKIT-side account settings
227
+ - OAuth, OTP, or provider linking from the local provider
270
228
  - workspace membership, user preferences, or the later users/workspaces data model
271
229
 
272
- Later, when the guide installs `users-web`, auth stops using the standalone fallback and starts resolving `users.profile.sync.service` from `users-core` instead.
273
-
274
- That service exposes three main functions:
230
+ Later, when an app needs the database-backed users layer, `users-core` can provide `auth.profile.projector` through `users.profile.sync.service`.
275
231
 
276
- - `findByIdentity(...)` to look up the JSKIT-side user for an auth identity
277
- - `upsertByIdentity(...)` to create or update the JSKIT-side user record directly
278
- - `syncIdentityProfile(...)` to run the normal auth-driven synchronization flow
232
+ That service projects a provider identity into the app-owned user/profile model. Local auth, Supabase auth, and future auth providers can all use that same projection seam.
279
233
 
280
- `syncIdentityProfile(...)` is the one auth actually relies on. It is the method that synchronizes the JSKIT-side user record, ensures the related settings row exists through `ensureForUserId(...)`, and then runs any registered post-sync lifecycle contributors.
281
-
282
- So this chapter gives you real authentication, but only a temporary app-side user mirror. The full persistent JSKIT user model comes later with the database and users layers.
234
+ So this chapter gives you real authentication, but it deliberately keeps the user model small. The full persistent JSKIT user model comes later with the database and users layers.
283
235
 
284
236
  ## Using auth in your own app
285
237
 
@@ -517,7 +469,7 @@ At this point the guide has shown three distinct layers of client state:
517
469
 
518
470
  That progression is intentional. Packages keep their operational runtimes internally, but the app-facing shared state they surface to Vue code is store-based.
519
471
 
520
- ## What `auth-base` adds to the app
472
+ ## What the local auth install adds to the app
521
473
 
522
474
  The interesting part of this chapter is that authentication appears in several different layers at once: environment config, public routing config, shell placements, and app-owned view wrappers.
523
475
 
@@ -532,7 +484,7 @@ The first new place to inspect is `package.json`:
532
484
  },
533
485
  "dependencies": {
534
486
  "@jskit-ai/auth-core": "0.x",
535
- "@jskit-ai/auth-provider-supabase-core": "0.x",
487
+ "@jskit-ai/auth-provider-local-core": "0.x",
536
488
  "@jskit-ai/auth-web": "0.x"
537
489
  }
538
490
  }
@@ -540,20 +492,25 @@ The first new place to inspect is `package.json`:
540
492
 
541
493
  Three things are worth noticing immediately.
542
494
 
543
- - `auth-provider-supabase-core` is the provider-specific runtime.
495
+ - `auth-core` owns the provider-neutral contract, shared actions, policy hooks, and capability normalization.
496
+ - `auth-provider-local-core` is the selected provider-specific runtime.
544
497
  - `auth-web` is the part that adds the web routes and the default auth UI.
545
498
  - there is an `auth` surface-specific dev/build script family, just as `home` already had.
546
499
 
547
- The provider command also writes a new `.env` file:
500
+ The provider package also writes local auth settings into `.env`:
548
501
 
549
502
  ```dotenv
550
- AUTH_PROVIDER=supabase
551
- AUTH_SUPABASE_URL=https://YOUR-PROJECT.supabase.co
552
- AUTH_SUPABASE_PUBLISHABLE_KEY=sb_publishable_...
553
- APP_PUBLIC_URL=http://localhost:5173
503
+ AUTH_PROVIDER=local
504
+ AUTH_LOCAL_BACKEND=file
554
505
  ```
555
506
 
556
- This is the bridge between the scaffold and your real Supabase project. `APP_PUBLIC_URL` matters because auth emails and callback flows need to know which browser URL they should return to.
507
+ `AUTH_PROVIDER=local` is the selected-provider marker. `AUTH_LOCAL_BACKEND=file` tells the local provider to use its built-in file backend. No database URL, Supabase URL, or provider key is needed for this chapter.
508
+
509
+ The backend selection is intentionally local-provider-specific. `auth.local.backend` is the public server container token for replacing the file store later; Supabase and other external providers do not use it. A custom local backend must expose a single `withTransaction(callback)` method. JSKIT calls that method for register, login, recovery, password reset, profile update, session revocation, and security operations, and the callback receives transactional `users`, `sessions`, and `recovery` repositories.
510
+
511
+ The built-in file backend is for low-friction local development, first deploys with one persistent Node instance, and simple apps that accept that storage model. It writes `.jskit/auth/*.passwd` files behind a store-level lock. Do not treat it as horizontally scalable or serverless-safe storage; when the app needs replicated auth storage, register a custom `auth.local.backend` that uses the app's chosen database runtime.
512
+
513
+ If you later configure local SMTP password recovery, also set `APP_PUBLIC_URL` so reset links point back to the browser URL for this app.
557
514
 
558
515
  Public routing config changes too. `config/public.js` has a second surface:
559
516
 
@@ -571,18 +528,9 @@ config.surfaceDefinitions.auth = {
571
528
 
572
529
  That `requiresAuth: false` line is important. The auth surface must stay public, otherwise users would need to be logged in before they could reach the login page.
573
530
 
574
- `config/server.js` also gets an auth stub:
575
-
576
- ```js
577
- config.auth ||= {};
578
- config.auth.profileMode = "standalone";
579
- config.auth.oauth = {
580
- providers: [],
581
- defaultProvider: ""
582
- };
583
- ```
531
+ The local provider does not need an app database profile mode or OAuth config. The stock login UI asks `/api/session` for the active provider capabilities and renders only what the selected provider actually supports. With the local provider, that means email/password login, registration, sign-out, session refresh, and password recovery when recovery is configured.
584
532
 
585
- That small block explains a lot of the default login screen and auth profile behavior. `profileMode: "standalone"` keeps this auth-only chapter on the temporary app-side profile mirror. The stock UI is ready for OAuth providers such as Google, but this chapter keeps the provider list empty, so the page only shows the email/password and one-time-code flows. Later, if you enable a provider in Supabase and list it here, the same login screen can expose that button too.
533
+ Later, if you switch to Supabase, the Supabase provider can append the app-owned OAuth visibility config and profile mode settings it needs.
586
534
 
587
535
  The auth routes themselves are app-owned wrappers around the module-supplied default views. `src/pages/auth/login.vue` looks like this:
588
536
 
@@ -658,9 +606,8 @@ This is the shell placement system from the previous chapter doing real work aga
658
606
 
659
607
  So the auth story in this chapter is spread across clear responsibilities:
660
608
 
661
- - `.env` tells the provider runtime which Supabase project to talk to
609
+ - `.env` selects the local provider and its backend
662
610
  - `config/public.js` declares an `auth` surface
663
- - `config/server.js` exposes app-owned OAuth visibility settings
664
611
  - `src/pages/auth/*` gives the app real public auth routes
665
612
  - `src/placement.js` makes auth visible in the shell
666
613
 
@@ -731,24 +678,23 @@ For ordinary Vue component code there is usually no advantage to writing it this
731
678
 
732
679
  ### Who actually talks to whom
733
680
 
734
- The most important thing to understand is that the browser usually talks to **your app**, and your app talks to **Supabase**.
681
+ The most important thing to understand is that the browser talks to **your app**, and the app talks to the **selected auth provider** through the provider-neutral `authService` contract.
735
682
 
736
- For the normal email-and-password flow, the browser does **not** call Supabase directly. It posts to the app's own API routes such as `/api/login`. The JSKIT server then calls Supabase, receives the Supabase session, and turns that into HTTP-only cookies.
683
+ With the local provider, the selected provider is in the same Node process. It verifies passwords, writes sessions, and stores local auth state through `auth.local.backend`. The default backend writes the `.jskit/auth/*.passwd` files; a custom backend can use a database later without changing the login UI or the shared auth routes.
737
684
 
738
- That means there are really three actors in play:
685
+ That means there are two actors in the first-app flow:
739
686
 
740
687
  - the browser, which renders the login screen and submits forms
741
- - the JSKIT app server, which owns `/api/login`, `/api/oauth/complete`, `/api/session`, and the cookie-writing step
742
- - Supabase, which owns the real authentication backend, password verification, OAuth exchange, and auth user records
688
+ - the JSKIT app server, which owns `/api/login`, `/api/register`, `/api/password/*`, `/api/session`, the selected provider, and the cookie-writing step
743
689
 
744
- So the mental model should be:
690
+ So the default local mental model is:
745
691
 
746
692
  ```text
747
- browser -> JSKIT app -> Supabase
748
- browser <- JSKIT app <- Supabase
693
+ browser -> JSKIT app -> local auth provider
694
+ browser <- JSKIT app <- local auth provider
749
695
  ```
750
696
 
751
- For OAuth there is one extra bounce: the browser is redirected out to Supabase and then back again. But even there, the app still owns the start and completion steps.
697
+ If you later switch to Supabase, the same app routes still exist, but the selected provider implementation delegates to Supabase Auth.
752
698
 
753
699
  ### Password login: the full round trip
754
700
 
@@ -774,20 +720,19 @@ From there, the server-side flow is:
774
720
  1. `POST /api/login` hits the route registered by `auth-web`.
775
721
  2. `AuthController.login()` receives the request.
776
722
  3. `AuthWebService.login()` executes the internal action `auth.login.password`.
777
- 4. The Supabase auth service calls `supabase.auth.signInWithPassword(...)`.
778
- 5. Supabase returns a `user` object and a `session` object.
779
- 6. JSKIT syncs the app-side profile mirror from that Supabase user.
780
- 7. JSKIT writes the access and refresh tokens into HTTP-only cookies.
781
- 8. The API response sent back to the browser is intentionally small.
723
+ 4. `auth-core` routes that action to the selected provider's `authService.login(...)`.
724
+ 5. The local provider checks the password hash in its backend and creates a session record.
725
+ 6. JSKIT writes the access and refresh tokens into HTTP-only cookies.
726
+ 7. The API response sent back to the browser is intentionally small.
782
727
 
783
- The important detail is step 7. The browser does **not** receive the raw Supabase session tokens as normal application state. The server writes them into cookies instead:
728
+ The important detail is step 6. The browser does **not** receive raw session tokens as normal application state. The server writes them into HTTP-only cookies instead. With the local provider, those cookies are:
784
729
 
785
- - `sb_access_token`
786
- - `sb_refresh_token`
730
+ - `jskit_local_access_token`
731
+ - `jskit_local_refresh_token`
787
732
 
788
733
  Those cookies are HTTP-only and `sameSite: "lax"`, so the browser sends them back automatically on later requests, but normal client-side code cannot read them directly.
789
734
 
790
- The JSON response from `/api/login` is much smaller than the underlying Supabase session object:
735
+ The JSON response from `/api/login` stays small:
791
736
 
792
737
  ```json
793
738
  {
@@ -802,17 +747,17 @@ So the real password-login round trip is:
802
747
 
803
748
  ```text
804
749
  1. browser -> POST /api/login -> JSKIT app
805
- 2. JSKIT app -> supabase.auth.signInWithPassword(...)
806
- 3. Supabase -> JSKIT app: user + session
750
+ 2. JSKIT app -> selected auth provider login
751
+ 3. selected auth provider -> JSKIT app: profile + session
807
752
  4. JSKIT app -> browser: set HTTP-only cookies + { ok, username }
808
753
  5. browser -> GET /api/session
809
- 6. JSKIT app -> browser: { authenticated, username, csrfToken, ... }
754
+ 6. JSKIT app -> browser: { authenticated, username, csrfToken, authCapabilities, ... }
810
755
  7. browser redirects to the requested route
811
756
  ```
812
757
 
813
758
  ### OAuth login: the extra browser bounce
814
759
 
815
- OAuth is the case where the browser really does leave the app briefly, but the app still owns the edges of the flow.
760
+ Local auth does not expose OAuth buttons. If you later switch to Supabase and configure OAuth providers, OAuth is the case where the browser really does leave the app briefly, but the app still owns the edges of the flow.
816
761
 
817
762
  The first step is still browser -> app. If the login page shows a button such as `Continue with Google`, clicking it does **not** go straight to Supabase. It first goes to:
818
763
 
@@ -903,8 +848,8 @@ That is why the login page needs both browser-side logic and server-side routes.
903
848
  When the browser calls it, the server:
904
849
 
905
850
  - reads the auth cookies
906
- - checks whether the access token still looks valid
907
- - refreshes the session through Supabase if needed
851
+ - asks the selected provider to authenticate the request
852
+ - refreshes or reissues provider cookies when the provider returns a refreshed session
908
853
  - clears invalid cookies if the session is no longer usable
909
854
  - returns the auth state the client actually needs
910
855
 
@@ -915,6 +860,7 @@ The response is shaped roughly like this:
915
860
  "authenticated": true,
916
861
  "username": "alice",
917
862
  "csrfToken": "...",
863
+ "authCapabilities": { "...": "..." },
918
864
  "oauthProviders": [],
919
865
  "oauthDefaultProvider": null
920
866
  }
@@ -927,11 +873,11 @@ That explains why the login screen and auth guard runtime both care about `/api/
927
873
  - which OAuth buttons to render
928
874
  - which CSRF token to use for later writes
929
875
 
930
- It is also why the shell widget can react cleanly to auth state without storing raw session tokens in client state. The browser just asks the app for the current session view, and the app derives that from its cookies plus Supabase.
876
+ It is also why the shell widget can react cleanly to auth state without storing raw session tokens in client state. The browser just asks the app for the current session view, and the app derives that from its cookies plus the selected provider.
931
877
 
932
878
  ### Authenticated Playwright testing with the dev auth bypass
933
879
 
934
- JSKIT ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through Supabase.
880
+ JSKIT ships a development-only auth bootstrap path specifically so authenticated UI can be verified in Playwright without depending on a real live login flow through an external auth provider.
935
881
 
936
882
  This is the standard path the agent should use for authenticated browser tests:
937
883
 
@@ -1060,16 +1006,16 @@ That flow is preferable to driving the real sign-in form in feature tests becaus
1060
1006
  - use the local dev auth bypass or another local session bootstrap path
1061
1007
  - exercise the actual changed behavior, not only page load
1062
1008
 
1063
- ## What appears in Supabase
1009
+ ## When you later switch to Supabase
1064
1010
 
1065
- It is important to separate **Supabase auth data** from **JSKIT app-owned data**.
1011
+ This chapter does not create anything in Supabase. If you later switch the selected provider from local auth to Supabase, keep separating **Supabase auth data** from **JSKIT app-owned data**.
1066
1012
 
1067
- When a user registers from this screen, Supabase creates a real auth user immediately. According to Supabase's user-management docs, you can see users in two places.
1013
+ When a user registers through a Supabase-backed app, Supabase creates a real auth user. According to Supabase's user-management docs, you can see users in two places.
1068
1014
 
1069
1015
  - `Authentication -> Users` in the Supabase dashboard
1070
1016
  - the `auth` schema in the Table Editor
1071
1017
 
1072
- In practice, after someone registers you should expect to see at least these things on the Supabase side.
1018
+ In practice, after someone registers against Supabase you should expect to see at least these things on the Supabase side.
1073
1019
 
1074
1020
  - A user row exists in `auth.users`.
1075
1021
  - The email address appears there.
@@ -1079,14 +1025,14 @@ In practice, after someone registers you should expect to see at least these thi
1079
1025
 
1080
1026
  On the Dashboard's `Authentication -> Users` page, that usually means you will see a new user entry with the email address, creation time, last sign-in information once they have signed in, and confirmation state. If you open the user details, you can inspect the auth record more closely.
1081
1027
 
1082
- JSKIT's register flow also sends a starter `display_name` into Supabase user metadata. That means the new user can carry an initial display-name value in provider metadata even before the later database-backed users layer is installed.
1028
+ JSKIT's Supabase register flow also sends a starter `display_name` into Supabase user metadata. That means the new user can carry an initial display-name value in provider metadata even before the later database-backed users layer is installed.
1083
1029
 
1084
1030
  This is the key distinction for the chapter.
1085
1031
 
1086
- - Supabase already has a real user record.
1087
- - JSKIT's own mirrored profile/settings store is still the temporary standalone in-memory version.
1032
+ - Supabase owns the real auth user record.
1033
+ - JSKIT app-owned profile/settings rows are separate and only exist after you install the users/database layer.
1088
1034
 
1089
- So if you restart the local JSKIT server in this chapter, the temporary app-side mirror is rebuilt as users authenticate again, but the actual Supabase auth user is still there because that data lives in Supabase, not in your local Node process.
1035
+ So if you restart the local JSKIT server after switching to Supabase, the actual Supabase auth user is still there because that data lives in Supabase, not in your local Node process.
1090
1036
 
1091
1037
  One more subtle point matters here.
1092
1038
 
@@ -1097,6 +1043,6 @@ That is why the confirmation screen in the app should be understood as a **sessi
1097
1043
 
1098
1044
  ## Summary
1099
1045
 
1100
- After this chapter, the app can really authenticate against Supabase. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The provider-specific values live in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1046
+ After this chapter, the app can really authenticate without Supabase or a database. It has a public `auth` surface, a stock login page, a sign-out route, and a shell widget that reflects auth state. The selected provider value lives in `.env`, and the web auth layer is wired into the same placement and surface system introduced earlier in the guide.
1101
1047
 
1102
1048
  Just as importantly, the app is still deliberately incomplete. Authentication exists, but the database-backed user model does not. That separation is useful, because the next layer of the guide can explain users and persistence without having to also explain the first auth setup at the same time.