@sveltebase/auth 1.4.0 → 1.4.3

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.
Files changed (2) hide show
  1. package/README.md +38 -406
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,441 +1,73 @@
1
1
  # @sveltebase/auth
2
2
 
3
- A lightweight, Edge-native, DB-agnostic authentication library built for **Svelte 5** and **SvelteKit** that integrates seamlessly with **@sveltebase/sync** for real-time WebSocket session verification.
3
+ A lightweight, Edge-native, DB-agnostic authentication library for Svelte 5 and SvelteKit. It integrates with `@sveltebase/sync` for WebSocket session verification.
4
4
 
5
- ## Features
5
+ ## Sync Authentication
6
6
 
7
- - **Stateless JWT Sessions**: Session data is signed using HMAC-SHA256 and stored directly in secure, server-side `HttpOnly` cookies.
8
- - **DB-Agnostic Design**: Works out-of-the-box with D1 SQLite, Drizzle, Prisma, Postgres, or any other database of your choice.
9
- - **Fast, Database-Free SSR**: Page renders decode the signed cookie instantly without database queries or heavy cryptographic signature validation on initial loads.
10
- - **Svelte 5 Reactive Getters**: Hydrate client state with a reactive getter function (`() => data.user`) so that the UI reactively updates without writing `$effect` sync boilerplate in layouts.
11
- - **WebSocket Channel Verification**: Offloads token verification and database checkups to Sveltebase Sync's WebSocket connection, verifying credentials in the background.
12
- - **Automatic Offline Cache Wiping**: Automatically cleans up local IndexedDB database tables and terminates WebSocket connections on session expiry, account deletion, or user logout.
13
- - **Web Crypto API Native**: Built on top of `globalThis.crypto.subtle` (zero NPM dependencies) to run at maximum speed in Edge runtimes like Cloudflare Workers.
7
+ Use `jwtCookieAuth()` anywhere the sync WebSocket is upgraded. In one-worker Cloudflare apps, that is both the SvelteKit route used by Vite dev and the Worker wrapper used by Wrangler/prod.
14
8
 
15
- ---
16
-
17
- ## Architecture & Flows
18
-
19
- ### 1. Fast Page Load (SSR)
20
- ```
21
- Browser (Client) ------------[ GET /dashboard ]------------> SvelteKit Server (SSR)
22
-
23
- getUserFromCookie()
24
- (Fast JSON base64-decode)
25
-
26
- Browser (Client) <-----------[ HTML + page.data ]───────────────────┘
27
- ```
28
- SSR is instant because no database queries or cryptographic signature verifications are performed during the page load.
29
-
30
- ### 2. WebSocket Background Verification
31
- ```
32
- Browser (Client) ────────────[ Connect WebSocket ]───────────> Sync Engine (DO)
33
- │ │
34
- ├───────────────[ Subscribe: "users" channel ]───────────────>┤
35
- │ │
36
- │ verifyJWT()
37
- │ (Crypto verification)
38
- │ │
39
- │ verifyUser()
40
- │ (Database check)
41
- │ │
42
- │<──────────────[ Subscription Authorized ]───────────────────┤ (Success)
43
- │ │
44
- │<──────────────[ Subscription Error ]────────────────────────┘ (Fail)
45
-
46
- ▼ (If failed)
47
- auth.logout()
48
- (Wipes IndexedDB & redirects)
49
- ```
50
-
51
- ---
52
-
53
- ## 1. Installation
54
-
55
- Add the package to your SvelteKit project workspace:
56
- ```bash
57
- bun add @sveltebase/auth
58
- ```
59
-
60
- ---
61
-
62
- ## 2. Server Setup
63
-
64
- Configure the server-side authentication manager:
65
-
66
- ```typescript
67
- // src/lib/server/auth.ts
68
- import { createServerAuth } from "@sveltebase/auth";
69
- import { JWT_SECRET } from "$env/static/private";
70
-
71
- export interface AppUser {
72
- id: string;
73
- email: string;
74
- name: string;
75
- token: string; // Embedded JWT signature
76
- }
77
-
78
- export const auth = createServerAuth<AppUser>({
79
- jwtSecret: JWT_SECRET,
80
- cookieName: "sf_session",
81
- cookieOptions: {
82
- maxAge: 60 * 60 * 24 * 7, // 1 week
83
- path: "/",
84
- httpOnly: true,
85
- secure: true,
86
- sameSite: "lax"
87
- }
88
- });
89
- ```
90
-
91
- ---
92
-
93
- ## 3. SvelteKit Route Integration
94
-
95
- ### Server load (`src/routes/+layout.server.ts`)
96
- Retrieve the user payload from the cookies during page render:
97
-
98
- ```typescript
99
- import { getUserFromCookie } from "@sveltebase/auth";
100
- import type { AppUser } from "$lib/auth-client.js";
101
- import type { LayoutServerLoad } from "./$types";
102
-
103
- export const load: LayoutServerLoad = async ({ cookies }) => {
104
- // Sub-millisecond unverified JSON decode
105
- const user = getUserFromCookie<AppUser>(cookies);
106
- return { user };
107
- };
108
- ```
109
-
110
- ### Server endpoints (`src/routes/api/auth/login/+server.ts`)
111
- Set the session cookie upon credentials verification:
112
-
113
- ```typescript
114
- import { auth } from "$lib/server/auth.js";
115
- import { json } from "@sveltejs/kit";
116
-
117
- export const POST = async ({ cookies, request }) => {
118
- const credentials = await request.json();
119
-
120
- // 1. Verify credentials with your database
121
- const dbUser = await verifyCredentials(credentials);
122
- if (!dbUser) {
123
- return new Response("Invalid credentials", { status: 401 });
124
- }
125
-
126
- // 2. setSession creates the JWT, embeds it in user.token, and writes the cookie
127
- const sessionUser = await auth.login(cookies, {
128
- id: dbUser.id,
129
- email: dbUser.email,
130
- name: dbUser.name
131
- });
132
-
133
- return json({ success: true, user: sessionUser });
134
- };
135
- ```
136
-
137
- ---
138
-
139
- ## 4. Client Setup (Svelte 5)
140
-
141
- Initialize the client auth state, linking it to your `@sveltebase/sync` client:
142
-
143
- ```typescript
144
- // src/lib/auth-client.ts
145
- import { createAuth } from "@sveltebase/auth/client";
146
- import { sync } from "./sync-client.js"; // your Sveltebase Sync instance
147
- import type { AppUser } from "./server/auth.js";
9
+ ```ts
10
+ // src/routes/api/sync/+server.ts
11
+ import { jwtCookieAuth } from "@sveltebase/auth/sync";
12
+ import { syncEngineRoute } from "@sveltebase/sync/sveltekit";
13
+ import { handlers } from "$lib/server/sync-handlers";
148
14
 
149
- export const auth = createAuth<AppUser>({
150
- syncClient: sync // Handles automated channel monitoring & auto-logouts
15
+ export const { GET } = syncEngineRoute({
16
+ handlers,
17
+ auth: jwtCookieAuth(),
18
+ allowUnauthenticated: true,
151
19
  });
152
20
  ```
153
21
 
154
- ### Layout Binding (`src/routes/+layout.svelte`)
155
- Initialize the state once using a Svelte 5 reactive getter:
156
-
157
- ```svelte
158
- <script lang="ts">
159
- import { auth } from "$lib/auth-client";
160
- let { data, children } = $props();
161
-
162
- // Initialize once. It automatically tracks changes to data.user
163
- auth.init(() => data.user);
164
- </script>
165
-
166
- {@render children()}
167
- ```
168
-
169
- ---
170
-
171
- ## 5. Sync Worker Verification
172
-
173
- Sync authentication is verified inside the standalone sync Worker, not in the SvelteKit app Worker. The app Worker should proxy `/api/sync` to the sync Worker and should not forward resolved auth objects.
174
-
175
- Create the sync Worker with `jwtCookieAuth()`:
176
-
177
- ```typescript
178
- // src/worker/sync.ts
22
+ ```ts
23
+ // src/worker/app.ts
24
+ import app from "../../.svelte-kit/cloudflare/_worker.js";
179
25
  import { jwtCookieAuth } from "@sveltebase/auth/sync";
180
- import { defineSyncWorker, SyncEngine } from "@sveltebase/sync/cloudflare";
26
+ import { createSyncAppWorker, SyncEngine } from "@sveltebase/sync/cloudflare";
181
27
  import { handlers } from "$lib/server/sync-handlers";
182
28
 
183
- export default defineSyncWorker({
29
+ export default createSyncAppWorker(app, {
184
30
  handlers,
185
- auth: jwtCookieAuth()
31
+ auth: jwtCookieAuth(),
32
+ allowUnauthenticated: true,
186
33
  });
187
34
 
188
35
  export { SyncEngine };
189
36
  ```
190
37
 
191
- `jwtCookieAuth()` reads the `sf_session` cookie by default, verifies it with `platform.env.JWT_SECRET`, resolves identity from `user.id`, and marks unauthenticated websocket connections as rejected by default.
192
-
193
- Both Workers need the same secret:
194
-
195
- ```bash
196
- wrangler secret put JWT_SECRET --config wrangler.jsonc
197
- wrangler secret put JWT_SECRET --config wrangler.sync.jsonc
198
- ```
38
+ `jwtCookieAuth()` reads the `sf_session` cookie by default, verifies it with `platform.env.JWT_SECRET`, resolves identity from `user.id`, and rejects unauthenticated WebSocket upgrades unless `allowUnauthenticated: true` is passed to the sync setup.
199
39
 
200
- Register `createAuthSync()` on the sync Worker handlers to protect the `"users"` channel and optionally check the database:
40
+ Register `createAuthSync()` in your sync handlers to protect the `users` channel and optionally check the database:
201
41
 
202
- ```typescript
42
+ ```ts
203
43
  // src/lib/server/sync-handlers.ts
204
44
  import { createAuthSync } from "@sveltebase/auth/server";
205
- import { getDB } from "./db.js";
45
+ import { eq } from "drizzle-orm";
46
+ import { getDB } from "./db";
47
+ import { users } from "./schema";
206
48
 
207
49
  export const handlers = [
208
50
  createAuthSync({
209
51
  verifyUser: async (user, ctx) => {
210
52
  const db = getDB(ctx.platform);
211
- const activeUser = await db.select().from(users).where(eq(users.id, user.id)).get();
53
+ const activeUser = await db
54
+ .select()
55
+ .from(users)
56
+ .where(eq(users.id, user.id))
57
+ .get();
212
58
  return !!activeUser && !activeUser.isSuspended;
213
59
  },
214
60
  onUpdate: async (userId, changes, ctx) => {
215
61
  const db = getDB(ctx.platform);
216
- return await db.update(users).set(changes).where(eq(users.id, userId)).returning();
217
- }
62
+ const [updated] = await db
63
+ .update(users)
64
+ .set(changes)
65
+ .where(eq(users.id, userId))
66
+ .returning();
67
+ return updated;
68
+ },
218
69
  }),
219
- todoSync
220
70
  ];
221
71
  ```
222
72
 
223
- Other sync handlers can use `ctx.auth?.user` and `ctx.identity` for row ownership checks:
224
-
225
- ```typescript
226
- fetch: async (ctx) => {
227
- const user = ctx.auth?.user;
228
- if (!user) return [];
229
-
230
- return db.select().from(todos).where(eq(todos.userId, user.id));
231
- }
232
- ```
233
-
234
- Keep the browser connecting to the app origin so the session cookie is sent:
235
-
236
- ```typescript
237
- // src/routes/api/sync/+server.ts
238
- import { SYNC_WORKER_URL } from "$env/static/private";
239
- import { syncProxy } from "@sveltebase/sync/sveltekit";
240
-
241
- export const { GET, POST } = syncProxy({
242
- fallbackUrl: SYNC_WORKER_URL
243
- });
244
- ```
245
-
246
- ## 6. API Reference
247
-
248
- ### Server APIs (`@sveltebase/auth`)
249
-
250
- * **`createServerAuth<User>(config: AuthConfig)`**
251
- Creates cookie management helpers.
252
- * `login(cookies: Cookies, userPayload: Omit<User, 'token'>, options?): Promise<User>`: Encodes and writes session to cookie.
253
- * `logout(cookies: Cookies, options?): void`: Deletes the session cookie.
254
- * **`getUserFromCookie<User>(cookies: Cookies, cookieName?): User | null`**
255
- Decodes the cookie payload without signature verification (fast SSR).
256
- * **`getUserFromRequest<User>(request: Request, cookieName?): User | null`**
257
- Parses raw cookie header on standard web requests (e.g. Workers, Durable Objects).
258
- * **`getVerifiedUserFromRequest<User>(request: Request, jwtSecret: string, cookieName?): Promise<User | null>`**
259
- Parses request cookies and cryptographically verifies the token signature.
260
-
261
- ### Client APIs (`@sveltebase/auth/client`)
262
-
263
- * **`createAuth<User>(config: AuthClientConfig)`**
264
- Creates the client reactive state.
265
- * `get user(): User | null`: Returns the active user. Supports Svelte 5 reactive lookups.
266
- * `set user(value: User | null)`: Overrides local user state (optimistic mutations).
267
- * `isAuthenticated: boolean`: Helper check.
268
- * `init(user: MaybeGetter<User | null>)`: Binds layout page data.
269
- * `update(changes: Partial<User>): Promise<void>`: Optimistically updates user, syncs over WS, and rewrites the cookie.
270
- * `logout(): Promise<void>`: Deletes session and clears IndexedDB cache.
271
-
272
- ### Sync Server Handler (`@sveltebase/auth/server`)
273
-
274
- * **`createAuthSync(config: SyncAuthConfig)`**
275
- Establishes the `"users"` sync channel. Use `jwtCookieAuth()` in the standalone sync Worker to populate `ctx.auth` and `ctx.identity` for all sync handlers.
276
- * **`jwtCookieAuth(options?)`** from `@sveltebase/auth/sync`
277
- Verifies the `sf_session` cookie using `platform.env.JWT_SECRET` during the sync Worker websocket handshake.
278
-
279
- ---
280
-
281
- ## 7. Google OAuth Integration
282
-
283
- `@sveltebase/auth/google` provides a fully reactive Svelte 5 wrapper for the **Google Identity Services (GIS)** SDK. It allows you to embed official Google Sign-In buttons, use One Tap login, or trigger custom-styled login buttons.
284
-
285
- ### Wrapper Setup (`GoogleOAuthProvider`)
286
-
287
- Wrap your page or layout where you intend to use Google Sign-In with the `<GoogleOAuthProvider>` and pass your Google Client ID:
288
-
289
- ```svelte
290
- <script lang="ts">
291
- import { GoogleOAuthProvider } from "@sveltebase/auth/google";
292
- import MyLoginComponent from "./MyLoginComponent.svelte";
293
- </script>
294
-
295
- <GoogleOAuthProvider clientId="YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com">
296
- <MyLoginComponent />
297
- </GoogleOAuthProvider>
298
- ```
299
-
300
- ### Standard Sign-In Button (`GoogleLogin`)
301
-
302
- The `<GoogleLogin>` component renders the official iframe-based customizable button:
303
-
304
- ```svelte
305
- <!-- MyLoginComponent.svelte -->
306
- <script lang="ts">
307
- import { GoogleLogin } from "@sveltebase/auth/google";
308
- import type { CredentialResponse } from "@sveltebase/auth/google";
309
-
310
- function handleSuccess(response: CredentialResponse) {
311
- console.log("JWT credential token:", response.credential);
312
- // Send this credential to your backend to log in or create an account!
313
- }
314
-
315
- function handleError() {
316
- console.error("Sign-In failed");
317
- }
318
- </script>
319
-
320
- <GoogleLogin onSuccess={handleSuccess} onError={handleError} />
321
- ```
322
-
323
- ### Custom Styled Buttons (`createGoogleLogin`)
324
-
325
- For custom-styled login buttons, use the `createGoogleLogin` helper. It must be called in a child component of `<GoogleOAuthProvider>`. It returns a trigger `login` function alongside reactive `loading` and `error` states:
326
-
327
- ```svelte
328
- <!-- MyLoginComponent.svelte -->
329
- <script lang="ts">
330
- import { createGoogleLogin } from "@sveltebase/auth/google";
331
-
332
- const googleAuth = createGoogleLogin({
333
- flow: "implicit", // or 'auth-code'
334
- scope: "email profile", // additional scopes if needed
335
- onSuccess: (response) => {
336
- console.log("OAuth response:", response); // contains access_token or code
337
- },
338
- onError: (err) => {
339
- console.error("Auth error:", err);
340
- }
341
- });
342
- </script>
343
-
344
- <button
345
- onclick={() => googleAuth.login()}
346
- disabled={googleAuth.loading}
347
- class="custom-btn"
348
- >
349
- {#if googleAuth.loading}
350
- Logging in...
351
- {:else}
352
- Login with Google (Custom UI)
353
- {/if}
354
- </button>
355
-
356
- {#if googleAuth.error}
357
- <p class="error">Error: {googleAuth.error.message}</p>
358
- {/if}
359
- ```
360
-
361
- ### Google One Tap (`GoogleOneTapLogin`)
362
-
363
- Mount the `<GoogleOneTapLogin>` component to display Google's native One Tap prompt when the page loads:
364
-
365
- ```svelte
366
- <script lang="ts">
367
- import { GoogleOneTapLogin } from "@sveltebase/auth/google";
368
- import type { CredentialResponse } from "@sveltebase/auth/google";
369
- </script>
370
-
371
- <GoogleOneTapLogin
372
- onSuccess={(response: CredentialResponse) => console.log("One Tap JWT:", response.credential)}
373
- onError={() => console.error("One Tap dismissed/failed")}
374
- />
375
- ```
376
-
377
- ### Revoking/Logging Out (`googleLogout`)
378
-
379
- To sign out the user from the current Google session and disable automatic sign-in on future visits:
380
-
381
- ```typescript
382
- import { googleLogout } from "@sveltebase/auth/google";
383
-
384
- function logout() {
385
- googleLogout();
386
- // ... clear local user session state
387
- }
388
- ```
389
-
390
- ### Decoding Credentials (`decodeCredentials`)
391
-
392
- When using the `<GoogleLogin>` or `<GoogleOneTapLogin>` components, Google returns a credential string which is a signed JWT token containing the user's profile information. You can use the `decodeCredentials` function to decode this token on either the client or server into a typed `GoogleData` object:
393
-
394
- ```svelte
395
- <script lang="ts">
396
- import { GoogleLogin, decodeCredentials } from "@sveltebase/auth/google";
397
- import type { CredentialResponse, GoogleData } from "@sveltebase/auth/google";
398
-
399
- function handleSuccess(response: CredentialResponse) {
400
- if (response.credential) {
401
- const decoded: GoogleData = decodeCredentials(response.credential);
402
- console.log("Decoded user profile:", decoded);
403
- console.log("User email:", decoded.email);
404
- console.log("User name:", decoded.name);
405
- console.log("Profile picture:", decoded.picture);
406
- }
407
- }
408
- </script>
409
-
410
- <GoogleLogin onSuccess={handleSuccess} />
411
- ```
412
-
413
- ### Cryptographically Verifying ID Tokens (`verifyIdToken`)
414
-
415
- To securely verify the authenticity of a Google Identity credential (JWT) on your server (e.g. SvelteKit `+server.ts` or form actions), use `verifyIdToken`. It fetches Google's public JWK keys and cryptographically verifies the token's RS256 signature and standard claims using the Web Crypto API.
416
-
417
- > [!IMPORTANT]
418
- > This function **does not** require a Google Client Secret. It only requires your **Google Client ID** to verify the token's audience.
419
-
420
- ```typescript
421
- import { verifyIdToken } from "@sveltebase/auth/google";
422
-
423
- export const POST = async ({ request }) => {
424
- const { credential } = await request.json();
425
-
426
- try {
427
- const payload = await verifyIdToken({
428
- credential,
429
- clientId: "YOUR_GOOGLE_CLIENT_ID.apps.googleusercontent.com"
430
- });
431
-
432
- console.log("Verified user email:", payload.email);
433
- console.log("Verified user name:", payload.name);
434
-
435
- // Create session, log in the user, etc.
436
- return new Response(JSON.stringify({ success: true, user: payload }));
437
- } catch (err: any) {
438
- return new Response(err.message, { status: 400 });
439
- }
440
- };
441
- ```
73
+ Use `wrangler secret put JWT_SECRET --config wrangler.jsonc` for Wrangler/prod. Use `.env` for Vite dev when using `@sveltejs/adapter-cloudflare` platform proxy.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sveltebase/auth",
3
- "version": "1.4.0",
3
+ "version": "1.4.3",
4
4
  "type": "module",
5
5
  "publishConfig": {
6
6
  "access": "public"