@gauts/auth 0.8.2 → 0.10.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.
Files changed (97) hide show
  1. package/README.md +376 -140
  2. package/SECURITY.md +8 -4
  3. package/dist/adapters/drizzle/config.d.ts +11 -0
  4. package/dist/adapters/drizzle/config.d.ts.map +1 -0
  5. package/dist/adapters/drizzle/config.js +147 -0
  6. package/dist/adapters/drizzle/config.js.map +1 -0
  7. package/dist/adapters/drizzle/index.d.ts +4 -0
  8. package/dist/adapters/drizzle/index.d.ts.map +1 -0
  9. package/dist/adapters/drizzle/index.js +156 -0
  10. package/dist/adapters/drizzle/index.js.map +1 -0
  11. package/dist/adapters/drizzle/model.d.ts +21 -0
  12. package/dist/adapters/drizzle/model.d.ts.map +1 -0
  13. package/dist/adapters/drizzle/model.js +61 -0
  14. package/dist/adapters/drizzle/model.js.map +1 -0
  15. package/dist/adapters/drizzle/schema.d.ts +311 -0
  16. package/dist/adapters/drizzle/schema.d.ts.map +1 -0
  17. package/dist/adapters/drizzle/schema.js +37 -0
  18. package/dist/adapters/drizzle/schema.js.map +1 -0
  19. package/dist/adapters/drizzle/types.d.ts +109 -0
  20. package/dist/adapters/drizzle/types.d.ts.map +1 -0
  21. package/dist/adapters/drizzle/types.js +2 -0
  22. package/dist/adapters/drizzle/types.js.map +1 -0
  23. package/dist/adapters/express/index.d.ts +77 -0
  24. package/dist/adapters/express/index.d.ts.map +1 -0
  25. package/dist/adapters/express/index.js +72 -0
  26. package/dist/adapters/express/index.js.map +1 -0
  27. package/dist/adapters/express/social.d.ts +29 -0
  28. package/dist/adapters/express/social.d.ts.map +1 -0
  29. package/dist/adapters/express/social.js +44 -0
  30. package/dist/adapters/express/social.js.map +1 -0
  31. package/dist/adapters/fastify/index.d.ts +79 -0
  32. package/dist/adapters/fastify/index.d.ts.map +1 -0
  33. package/dist/adapters/fastify/index.js +100 -0
  34. package/dist/adapters/fastify/index.js.map +1 -0
  35. package/dist/adapters/fastify/social.d.ts +27 -0
  36. package/dist/adapters/fastify/social.d.ts.map +1 -0
  37. package/dist/adapters/fastify/social.js +48 -0
  38. package/dist/adapters/fastify/social.js.map +1 -0
  39. package/dist/adapters/hono/index.d.ts +10 -23
  40. package/dist/adapters/hono/index.d.ts.map +1 -1
  41. package/dist/adapters/hono/index.js +32 -224
  42. package/dist/adapters/hono/index.js.map +1 -1
  43. package/dist/adapters/hono/social.d.ts +4 -10
  44. package/dist/adapters/hono/social.d.ts.map +1 -1
  45. package/dist/adapters/hono/social.js +25 -221
  46. package/dist/adapters/hono/social.js.map +1 -1
  47. package/dist/adapters/model.d.ts +33 -0
  48. package/dist/adapters/model.d.ts.map +1 -0
  49. package/dist/adapters/model.js +87 -0
  50. package/dist/adapters/model.js.map +1 -0
  51. package/dist/adapters/next/index.d.ts +1 -1
  52. package/dist/adapters/next/index.d.ts.map +1 -1
  53. package/dist/adapters/next/index.js +7 -3
  54. package/dist/adapters/next/index.js.map +1 -1
  55. package/dist/adapters/prisma/config.d.ts.map +1 -1
  56. package/dist/adapters/prisma/config.js +1 -4
  57. package/dist/adapters/prisma/config.js.map +1 -1
  58. package/dist/adapters/prisma/model.d.ts +7 -8
  59. package/dist/adapters/prisma/model.d.ts.map +1 -1
  60. package/dist/adapters/prisma/model.js +11 -79
  61. package/dist/adapters/prisma/model.js.map +1 -1
  62. package/dist/adapters/utils/cookie.d.ts +17 -0
  63. package/dist/adapters/utils/cookie.d.ts.map +1 -0
  64. package/dist/adapters/utils/cookie.js +59 -0
  65. package/dist/adapters/utils/cookie.js.map +1 -0
  66. package/dist/adapters/utils/session.d.ts +4 -0
  67. package/dist/adapters/utils/session.d.ts.map +1 -0
  68. package/dist/adapters/utils/session.js +208 -0
  69. package/dist/adapters/utils/session.js.map +1 -0
  70. package/dist/adapters/utils/social.d.ts +51 -0
  71. package/dist/adapters/utils/social.d.ts.map +1 -0
  72. package/dist/adapters/utils/social.js +278 -0
  73. package/dist/adapters/utils/social.js.map +1 -0
  74. package/dist/adapters/utils/types.d.ts +61 -0
  75. package/dist/adapters/utils/types.d.ts.map +1 -0
  76. package/dist/adapters/utils/types.js +2 -0
  77. package/dist/adapters/utils/types.js.map +1 -0
  78. package/dist/config.d.ts +1 -2
  79. package/dist/config.d.ts.map +1 -1
  80. package/dist/config.js +1 -2
  81. package/dist/config.js.map +1 -1
  82. package/dist/index.d.ts +1 -1
  83. package/dist/session/cookie.d.ts +6 -8
  84. package/dist/session/cookie.d.ts.map +1 -1
  85. package/dist/session/cookie.js +7 -24
  86. package/dist/session/cookie.js.map +1 -1
  87. package/dist/session/state.d.ts +41 -0
  88. package/dist/session/state.d.ts.map +1 -0
  89. package/dist/session/state.js +192 -0
  90. package/dist/session/state.js.map +1 -0
  91. package/package.json +40 -3
  92. package/src/adapters/drizzle/schema.ts +47 -0
  93. package/src/adapters/prisma/schema.prisma +38 -0
  94. package/dist/session/cache.d.ts +0 -32
  95. package/dist/session/cache.d.ts.map +0 -1
  96. package/dist/session/cache.js +0 -138
  97. package/dist/session/cache.js.map +0 -1
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Database-backed password authentication, opaque browser sessions, and optional social authentication for Node.js applications.
4
4
 
5
- `@gauts/auth` provides password hashing, session lifecycle, secure cookies, database validation, optional short caching, Prisma persistence, Hono integration, and Google/GitHub/X OAuth. The application keeps control of credential lookup, business-specific registration data, authorization, responses, and UI.
5
+ `@gauts/auth` provides password hashing, session lifecycle, secure cookies, database validation, optional short caching, Prisma or Drizzle persistence, Hono/Express/Fastify integration, and Google/GitHub/X OAuth. The application keeps control of credential lookup, business-specific registration data, authorization, responses, and UI.
6
6
 
7
7
  ## Features
8
8
 
@@ -12,14 +12,17 @@ Database-backed password authentication, opaque browser sessions, and optional s
12
12
  | bcrypt password hashing | ✅ | Opt-in |
13
13
  | Opaque server-side sessions | ✅ | Enabled |
14
14
  | Database-backed validation | ✅ | Enabled |
15
- | Sliding session renewal | ✅ | Every 24 hours |
15
+ | Automatic sliding renewal | ✅ | Every 24 hours |
16
16
  | Absolute session lifetime | ✅ | 30 days |
17
17
  | Signed browser cache | ✅ | Disabled |
18
18
  | Full User-Agent validation | ✅ | Enabled |
19
19
  | IP validation | ✅ | Disabled |
20
20
  | Platform validation | ✅ | Disabled |
21
21
  | Hono adapter | ✅ | Available |
22
+ | Express adapter | ✅ | Available |
23
+ | Fastify adapter | ✅ | Available |
22
24
  | Prisma adapter | ✅ | Available |
25
+ | Drizzle MySQL/MariaDB adapter | ✅ | Available |
23
26
  | Next.js renewal adapter | ✅ | Available |
24
27
  | Google social authentication | ✅ | Opt-in |
25
28
  | GitHub social authentication | ✅ | Opt-in |
@@ -32,33 +35,46 @@ Database-backed password authentication, opaque browser sessions, and optional s
32
35
  | OTP and transactional email | ❌ | Application-owned |
33
36
  | Route roles and permissions | ❌ | Application-owned |
34
37
 
35
- “Session renewal” extends the existing session expiry when activity continues. It is not a refresh-token flow and does not rotate the opaque browser token.
38
+ “Session renewal” extends the existing session expiry when authenticated activity continues. Framework middleware performs it inline when due. It is not a refresh-token flow and does not rotate the opaque browser token.
36
39
 
37
40
  ## Quick start
38
41
 
39
- The application defines its credential login, renewal, logout, and protected endpoints. Optional addons are configured separately after this base setup.
42
+ The application defines its credential login, logout, protected endpoints, and the optional explicit renewal endpoint used by server-rendered frontends. Optional addons are configured separately after this base setup.
40
43
 
41
44
  ### 1. Install the package
42
45
 
43
- Run these commands inside the Hono API project:
46
+ Install the package and the framework used by the API. Only one framework adapter is required:
44
47
 
45
48
  ```bash
46
- npm install @gauts/auth
47
- npm install hono @prisma/client
49
+ npm install @gauts/auth hono
50
+ npm install @gauts/auth express
51
+ npm install @gauts/auth fastify
48
52
  ```
49
53
 
50
- When the Next.js frontend is a separate project, run these commands inside the frontend project:
54
+ When the Next.js frontend is a separate project, run this command inside the frontend project:
51
55
 
52
56
  ```bash
53
- npm install @gauts/auth
54
- npm install next
57
+ npm install @gauts/auth next
55
58
  ```
56
59
 
57
- `hono`, `@prisma/client`, and `next` only need to be installed when the corresponding project does not already provide them. The API imports `@gauts/auth/hono` and `@gauts/auth/prisma`; the frontend imports `@gauts/auth/next`.
60
+ Run only the matching API command. `hono`, `express`, `fastify`, and `next` only need to be installed when the corresponding project does not already provide them.
58
61
 
59
- ### 2. Add the Prisma schema
62
+ Express TypeScript projects also install its type declarations:
60
63
 
61
- The default adapter uses this fixed relationship tree:
64
+ ```bash
65
+ npm install --save-dev @types/express
66
+ ```
67
+
68
+ ### 2. Choose the database adapter and schema
69
+
70
+ One database adapter is required. Prisma and Drizzle are equivalent persistence choices; install and configure only one.
71
+
72
+ | Adapter | Database support | Setup |
73
+ | ------- | -------------------------- | ---------------------------------------------- |
74
+ | Prisma | Prisma-supported databases | [Use Prisma](#option-a--prisma) |
75
+ | Drizzle | MySQL and MariaDB | [Use Drizzle](#option-b--drizzle-mysqlmariadb) |
76
+
77
+ Both schemas use the same relationship tree:
62
78
 
63
79
  ```text
64
80
  users
@@ -66,70 +82,87 @@ users
66
82
  └── account_sessions
67
83
  ```
68
84
 
69
- Add the three required models to the API schema. Social authentication is an optional addon with its own schema instructions later in this README.
85
+ Social authentication is an optional addon with separate schema instructions later in this README.
70
86
 
71
- ```prisma
72
- model users {
73
- id String @id @default(uuid()) @db.VarChar(255)
74
- name String @db.VarChar(255)
87
+ #### Option A — Prisma
75
88
 
76
- accounts user_accounts[]
77
- }
89
+ Install Prisma inside the API project:
78
90
 
79
- model user_accounts {
80
- id String @id @default(uuid()) @db.VarChar(255)
81
- user_id String @db.VarChar(255)
82
- email String @unique @db.VarChar(255)
83
- password_hash String? @db.VarChar(255)
91
+ ```bash
92
+ npm install @prisma/client
93
+ npm install --save-dev prisma
94
+ ```
84
95
 
85
- user users @relation(fields: [user_id], references: [id], onDelete: Cascade)
86
- sessions account_sessions[]
96
+ [View and copy the Prisma schema](./src/adapters/prisma/schema.prisma) into the application's Prisma schema.
87
97
 
88
- @@index([user_id])
89
- }
98
+ Create the migration through the application's Prisma workflow, then regenerate its client:
99
+
100
+ ```bash
101
+ npx prisma migrate dev --name add_auth
102
+ npx prisma generate
103
+ ```
90
104
 
91
- model account_sessions {
92
- id String @id @default(uuid()) @db.VarChar(255)
93
- account_id String @db.VarChar(255)
94
- token_hash String @unique @db.VarChar(64)
95
- ip String? @db.VarChar(45)
96
- country String? @db.VarChar(2)
97
- platform String? @db.VarChar(255)
98
- agent String? @db.Text
99
- expires_at DateTime @db.Timestamp(0)
100
- revoked_at DateTime? @db.Timestamp(0)
101
- created_at DateTime @default(now()) @db.Timestamp(0)
102
- updated_at DateTime? @db.Timestamp(0)
105
+ Create the database adapter:
103
106
 
104
- account user_accounts @relation(fields: [account_id], references: [id], onDelete: Cascade)
107
+ ```ts
108
+ import { createPrismaAdapter } from "@gauts/auth/prisma";
105
109
 
106
- @@index([account_id])
107
- @@index([expires_at])
108
- @@index([revoked_at])
109
- }
110
+ import { prisma } from "./db.js";
111
+
112
+ export const authDb = createPrismaAdapter({
113
+ client: prisma,
114
+ });
110
115
  ```
111
116
 
112
- Create the migration through the application's Prisma workflow, then regenerate its client:
117
+ #### Option B Drizzle MySQL/MariaDB
118
+
119
+ Install Drizzle and the application's MySQL driver inside the API project:
113
120
 
114
121
  ```bash
115
- npx prisma migrate dev --name add_auth
116
- npx prisma generate
122
+ npm install drizzle-orm mysql2
123
+ npm install --save-dev drizzle-kit
124
+ ```
125
+
126
+ [View and copy the Drizzle MySQL/MariaDB schema](./src/adapters/drizzle/schema.ts) into the application.
127
+
128
+ Create and apply the migration through the application's Drizzle workflow, then create the database adapter:
129
+
130
+ ```ts
131
+ import { createDrizzleAdapter } from "@gauts/auth/drizzle";
132
+
133
+ import { db } from "./db.js";
134
+ import { accountSessions, userAccounts, users } from "./schema.js";
135
+
136
+ export const authDb = createDrizzleAdapter({
137
+ client: db,
138
+ models: {
139
+ accounts: { table: userAccounts },
140
+ sessions: { table: accountSessions },
141
+ users: { table: users },
142
+ },
143
+ });
117
144
  ```
118
145
 
119
146
  ### 3. Create the auth instance and routes
120
147
 
121
- Create the API auth instance:
148
+ This Hono example is identical for Prisma and Drizzle:
149
+
150
+ Generate one high-entropy API secret and keep it server-side:
151
+
152
+ ```bash
153
+ openssl rand -base64 48
154
+ ```
155
+
156
+ Store the result as `AUTH_SECRET` in the API environment. Do not expose it through a public frontend variable.
122
157
 
123
158
  ```ts
124
159
  import { createHonoAuth } from "@gauts/auth/hono";
125
- import { createPrismaAdapter } from "@gauts/auth/prisma";
126
160
 
127
- import { prisma } from "./db.js";
161
+ import { authDb } from "./authDb.js";
128
162
 
129
163
  export const auth = createHonoAuth({
130
- db: createPrismaAdapter({
131
- client: prisma,
132
- }),
164
+ db: authDb,
165
+ secret: process.env.AUTH_SECRET!,
133
166
  });
134
167
  ```
135
168
 
@@ -142,7 +175,7 @@ renewal every 24 hours
142
175
  max lifetime 30 days
143
176
  validation User-Agent
144
177
  cache disabled
145
- cookies __ses, __cac, __ren
178
+ cookies __ses, __ctx
146
179
  ```
147
180
 
148
181
  Mount the application-owned login routes and the package middleware:
@@ -202,6 +235,16 @@ app.get("/account", auth.requireSession, (c) => {
202
235
  });
203
236
  ```
204
237
 
238
+ The equivalent framework examples are available here:
239
+
240
+ | Framework | Simple example | Advanced example |
241
+ | --------- | --------------------------------------------------------------- | ------------------------------------------------------------------- |
242
+ | Hono | [`examples/hono/simple`](./examples/hono/simple/index.ts) | [`examples/hono/advanced`](./examples/hono/advanced/index.ts) |
243
+ | Express | [`examples/express/simple`](./examples/express/simple/index.ts) | [`examples/express/advanced`](./examples/express/advanced/index.ts) |
244
+ | Fastify | [`examples/fastify/simple`](./examples/fastify/simple/index.ts) | [`examples/fastify/advanced`](./examples/fastify/advanced/index.ts) |
245
+
246
+ Express exposes authenticated values through `response.locals`. Fastify exposes them through request decorators and requires one `auth.decorate(app)` call before routes are registered. All routes remain application-owned.
247
+
205
248
  When `storedHash` is missing, the package performs password work with the configured algorithm and always returns `false`. Applications do not need a dummy hash. Keep the response identical for unknown accounts and incorrect passwords.
206
249
 
207
250
  ### 4. Connect the Next.js frontend
@@ -221,7 +264,6 @@ Call it from the Next.js middleware (proxy.ts) on protected routes:
221
264
  ```ts
222
265
  import type { NextRequest } from "next/server";
223
266
  import { NextResponse } from "next/server";
224
-
225
267
  import { nextAuth } from "./lib/auth.js";
226
268
 
227
269
  export const proxy = async (request: NextRequest) => {
@@ -241,22 +283,28 @@ export const proxy = async (request: NextRequest) => {
241
283
  };
242
284
  ```
243
285
 
244
- The application owns the redirect URL. When `unauthorizedUrl` is provided, the adapter creates the redirect and copies every API `Set-Cookie` header to it before returning the final response. The frontend does not receive `AUTH_SECRET`; the API remains responsible for session validation.
286
+ The application owns the redirect URL. When `unauthorizedUrl` is provided, the adapter creates the redirect and copies every API `Set-Cookie` header to it before returning the final response. The frontend does not receive `AUTH_SECRET`; it reads only untrusted scheduling dates from `__ctx`. The API verifies the signature and remains responsible for session validation.
245
287
 
246
288
  ## Requirements and package entry points
247
289
 
248
290
  - Node.js 22 or newer.
249
291
  - A database adapter.
292
+ - Drizzle ORM 0.45.2 or newer when using the Drizzle adapter.
250
293
  - Hono 4 when using the Hono adapter.
294
+ - Express 5 when using the Express adapter.
295
+ - Fastify 5 when using the Fastify adapter.
251
296
  - Next.js 15 or newer when using the Next.js adapter.
252
297
 
253
- `hono` and `next` are optional peer dependencies. The Prisma adapter receives the application's generated Prisma client and does not import Prisma at runtime.
298
+ `drizzle-orm`, `hono`, `express`, `fastify`, and `next` are optional peer dependencies. Installing or importing one adapter does not load the others. The Prisma adapter receives the application's generated Prisma client and does not import Prisma at runtime. The Drizzle adapter imports `drizzle-orm`, while the application owns the MySQL/MariaDB driver.
254
299
 
255
300
  | Import | Purpose |
256
301
  | ----------------------- | --------------------------------------------------------- |
257
302
  | `@gauts/auth` | Password service, session core, errors, and public types. |
303
+ | `@gauts/auth/drizzle` | Drizzle database adapter for MySQL and MariaDB. |
258
304
  | `@gauts/auth/prisma` | Prisma database adapter. |
259
305
  | `@gauts/auth/hono` | Hono cookies, methods, and middleware. |
306
+ | `@gauts/auth/express` | Express cookies, methods, and middleware. |
307
+ | `@gauts/auth/fastify` | Fastify cookies, methods, decorators, and hooks. |
260
308
  | `@gauts/auth/next` | Next.js renewal scheduling and `Set-Cookie` forwarding. |
261
309
  | `@gauts/auth/providers` | Google, GitHub, and X OAuth providers. |
262
310
 
@@ -272,7 +320,7 @@ The application owns the redirect URL. When `unauthorizedUrl` is provided, the a
272
320
  | `session` | `SessionConfig` | ❌ | Session defaults | Expiry, renewal, and client validation configuration. |
273
321
  | `cookie` | `HonoCookieConfig` | ❌ | Cookie defaults | Names, domain, path, SameSite, and Secure settings. |
274
322
  | `cache` | `{ ttl: number }` | ❌ | Disabled | Enables the short signed browser cache. |
275
- | `secret` | `string` | With `cache` or `social` | — | HMAC secret for signed authentication data. Minimum 32 UTF-8 bytes. |
323
+ | `secret` | `string` || — | HMAC secret for signed session context and optional social data. Minimum 32 UTF-8 bytes. |
276
324
  | `social` | `SocialConfig` | ❌ | Disabled | Enables configured social providers, redirects, and optional registration. |
277
325
 
278
326
  ```ts
@@ -281,6 +329,8 @@ type HonoGetIp = (c: Context) => Promise<string | null | undefined> | string | n
281
329
 
282
330
  When `getIp` is omitted, the adapter stores `ip: null` and does not read IP headers automatically. Configuring `session.validation` with `"ip"` requires `getIp` and fails during initialization when it is missing.
283
331
 
332
+ `createExpressAuth()` and `createFastifyAuth()` accept the same configuration. Their `getIp` callback receives the native Express or Fastify request instead of a Hono context.
333
+
284
334
  ### Password
285
335
 
286
336
  #### Argon2id
@@ -363,17 +413,16 @@ renew_at = min((updated_at ?? created_at) + renewInterval, maxExpiresAt)
363
413
 
364
414
  ### Cookies
365
415
 
366
- | Property | Type / allowed values | Default | Description |
367
- | ------------- | ----------------------------- | ----------------- | ------------------------------------------------------------------------- |
368
- | `sessionName` | Valid cookie name | `"__ses"` | Contains the opaque token. This is the only authenticating cookie. |
369
- | `cacheName` | Valid cookie name | `"__cac"` | Contains the optional signed short cache. |
370
- | `renewName` | Valid cookie name | `"__ren"` | Contains the untrusted `renew_at` Unix timestamp. |
371
- | `domain` | `string` | Browser host only | Optional cookie domain. |
372
- | `path` | String beginning with `/` | `"/"` | Cookie path. |
373
- | `sameSite` | `"Strict" \| "Lax" \| "None"` | `"Lax"` | Browser SameSite policy. |
374
- | `secure` | `boolean` | `true` | Requires HTTPS when enabled. Set `false` only for local HTTP development. |
416
+ | Property | Type / allowed values | Default | Description |
417
+ | ------------- | ----------------------------- | ----------------- | --------------------------------------------------------------------------------------------- |
418
+ | `sessionName` | Valid cookie name | `"__ses"` | Contains the opaque token. This is the only authenticating cookie. |
419
+ | `contextName` | Valid cookie name | `"__ctx"` | Contains signed renewal scheduling and the optional short cache. It never authenticates alone. |
420
+ | `domain` | `string` | Browser host only | Optional cookie domain. |
421
+ | `path` | String beginning with `/` | `"/"` | Cookie path. |
422
+ | `sameSite` | `"Strict" \| "Lax" \| "None"` | `"Lax"` | Browser SameSite policy. |
423
+ | `secure` | `boolean` | `true` | Requires HTTPS when enabled. Set `false` only for local HTTP development. |
375
424
 
376
- All three cookies are always `HttpOnly` and expire with their respective server-side purpose. Their names must be unique.
425
+ Both cookies are always `HttpOnly`, expire with the authoritative session, and must use unique names. Deleting `__ctx` does not log the user out: the next authenticated API request validates `__ses` through the database and rebuilds the signed context. Deleting `__ses` ends browser authentication because `__ctx` is never accepted on its own.
377
426
 
378
427
  Cookie prefix rules are enforced:
379
428
 
@@ -393,53 +442,50 @@ The resolved names are available through:
393
442
 
394
443
  ```ts
395
444
  auth.cookie.sessionName; // "__ses"
396
- auth.cookie.cacheName; // "__cac"
397
- auth.cookie.renewName; // "__ren"
445
+ auth.cookie.contextName; // "__ctx"
398
446
  ```
399
447
 
400
- ### Signed cache
448
+ ### Signed session context
401
449
 
402
- | Property | Type / allowed values | Default | Description |
403
- | ----------- | ----------------------------------- | -------- | --------------------------------------------------------- |
404
- | `cache.ttl` | Integer `1` to `session.ttl` | Disabled | Maximum cache lifetime in seconds. |
405
- | `secret` | String with at least 32 UTF-8 bytes | — | Signs the cache with HMAC-SHA-256. Required with `cache`. |
450
+ | Property | Type / allowed values | Default | Description |
451
+ | ----------- | ----------------------------------- | -------- | ------------------------------------------------------------------ |
452
+ | `secret` | String with at least 32 UTF-8 bytes | Required | Signs `__ctx` with HMAC-SHA-256 and binds it to the opaque token. |
453
+ | `cache.ttl` | Integer `1` to `session.ttl` | Disabled | Enables cached account/session data for at most this many seconds. |
406
454
 
407
- The cache:
455
+ The signed context always contains the session expiry and next renewal time. When caching is configured, it may also contain the selected account and session data. The context:
408
456
 
409
- - is cryptographically bound to the opaque token and client identity;
410
- - is accepted only for `GET` and `HEAD`;
411
- - never extends the authoritative database session;
412
- - is bypassed for unsafe methods, renewal, logout, WebSockets, and core calls;
413
- - falls back to normal database authentication when absent, expired, malformed, or altered.
457
+ - is cryptographically bound to `__ses` and cannot authenticate without it;
458
+ - is verified by the API before any cached data is trusted;
459
+ - exposes untrusted `renew` and `exp` scheduling values to the Next.js adapter without exposing `AUTH_SECRET`;
460
+ - uses cached data only for `GET` and `HEAD`;
461
+ - never extends the authoritative database session by itself;
462
+ - bypasses cached data for unsafe methods, renewal, logout, WebSockets, and core calls;
463
+ - triggers normal database authentication and a fresh signed context when absent, expired, malformed, altered, or bound to another token.
414
464
 
415
- The cache is signed but not encrypted. Do not place passwords, password hashes, raw session tokens, or application secrets in account/session data.
465
+ The context is signed but not encrypted. Do not place passwords, password hashes, raw session tokens, or application secrets in selected account/session data.
416
466
 
417
467
  <details>
418
- <summary>Internal compact cache payload</summary>
468
+ <summary>Internal signed context payload</summary>
419
469
 
420
470
  ```ts
421
471
  {
422
- exp: cacheExpiresAt,
423
- acc: {
424
- id,
425
- email,
426
- name,
427
- role,
428
- status,
429
- timezone,
430
- user: { id, role, status },
431
- },
432
- ses: {
433
- id,
434
- client: { ip, agent, platform },
435
- created_at,
436
- exp: expiresAt,
437
- ren: renewAt,
438
- },
472
+ cache: {
473
+ data: {
474
+ account,
475
+ session: {
476
+ client,
477
+ created,
478
+ id,
479
+ },
480
+ },
481
+ exp: cacheExpiresAt,
482
+ } | null,
483
+ exp: sessionExpiresAt,
484
+ renew: renewAt,
439
485
  }
440
486
  ```
441
487
 
442
- `session.account_id` is reconstructed from `acc.id` after signature validation.
488
+ The payload is encoded as `base64url(payload).signature`. The Next.js adapter may decode `exp` and `renew` only as scheduling hints. The API verifies the signature against the current opaque token before using any value. `session.account_id` is reconstructed from `account.id` after verification.
443
489
 
444
490
  </details>
445
491
 
@@ -453,6 +499,8 @@ users
453
499
  └── account_sessions
454
500
  ```
455
501
 
502
+ [View and copy the required Prisma schema](./src/adapters/prisma/schema.prisma).
503
+
456
504
  The required default delegate names are `prisma.users`, `prisma.user_accounts`, and `prisma.account_sessions`. The fixed Prisma relation fields are:
457
505
 
458
506
  - `user_accounts.user`;
@@ -555,6 +603,77 @@ access: {
555
603
 
556
604
  Every configured account and user condition must match. Omitting `access` applies no application-specific account restriction.
557
605
 
606
+ ### Drizzle MySQL/MariaDB adapter
607
+
608
+ The Drizzle adapter supports MySQL and MariaDB. Install it inside the API project with the driver used by the application:
609
+
610
+ ```bash
611
+ npm install @gauts/auth drizzle-orm mysql2
612
+ ```
613
+
614
+ Unlike Prisma, Drizzle does not expose model delegates that can be discovered by name. Pass the application's table objects explicitly:
615
+
616
+ ```ts
617
+ import { createDrizzleAdapter } from "@gauts/auth/drizzle";
618
+ import { createHonoAuth } from "@gauts/auth/hono";
619
+ import { db } from "./db.js";
620
+ import { accountSessions, userAccounts, users } from "./schema.js";
621
+
622
+ export const auth = createHonoAuth({
623
+ db: createDrizzleAdapter({
624
+ client: db,
625
+ models: {
626
+ accounts: { table: userAccounts },
627
+ sessions: { table: accountSessions },
628
+ users: { table: users },
629
+ },
630
+ }),
631
+ secret: process.env.AUTH_SECRET!,
632
+ });
633
+ ```
634
+
635
+ The minimum Drizzle schema uses the same relationship tree and canonical TypeScript field names as the Prisma adapter. [View and copy the required Drizzle MySQL/MariaDB schema](./src/adapters/drizzle/schema.ts).
636
+
637
+ The adapter queries these tables through explicit SQL joins, so Drizzle `relations()` declarations are not required. Physical SQL column names may use aliases, but the TypeScript keys shown above are part of the adapter contract. Date columns must use `{ mode: "date" }`.
638
+
639
+ `select` and `access` work exactly like the Prisma adapter and are inferred from the supplied table:
640
+
641
+ ```ts
642
+ const database = createDrizzleAdapter({
643
+ client: db,
644
+ models: {
645
+ accounts: {
646
+ access: {
647
+ role: ["OWNER", "ADMIN"],
648
+ status: ["ACTIVE"],
649
+ },
650
+ select: ["role", "status", "timezone"],
651
+ table: userAccounts,
652
+ },
653
+ sessions: { table: accountSessions },
654
+ users: {
655
+ access: { status: ["ACTIVE", "PENDING"] },
656
+ select: ["status"],
657
+ table: users,
658
+ },
659
+ },
660
+ });
661
+ ```
662
+
663
+ | Property | Required | Description |
664
+ | ------------------------ | :------: | ---------------------------------------------------------------------- |
665
+ | `client` | ✅ | Drizzle MySQL client created by the application. |
666
+ | `models.users.table` | ✅ | Table with `id` and `name` columns. |
667
+ | `models.users.select` | ❌ | Additional public scalar fields; `id` and `name` are always included. |
668
+ | `models.users.access` | ❌ | Required values for the owning user/entity. |
669
+ | `models.accounts.table` | ✅ | Table with `id`, `email`, and `user_id` columns. |
670
+ | `models.accounts.select` | ❌ | Additional public scalar fields; `id` and `email` are always included. |
671
+ | `models.accounts.access` | ❌ | Required values for the authenticating account. |
672
+ | `models.sessions.table` | ✅ | Table implementing the complete documented session column contract. |
673
+ | `models.socials.table` | ❌ | Enables social persistence when the optional social table is supplied. |
674
+
675
+ Private password fields are rejected from `select` and `access` by both TypeScript and runtime validation. The adapter does not load or execute the Prisma adapter, and Prisma is not required in a Drizzle application.
676
+
558
677
  ### Next.js adapter
559
678
 
560
679
  ```ts
@@ -565,11 +684,11 @@ export const nextAuth = createNextAuth({
565
684
  });
566
685
  ```
567
686
 
568
- | Property | Type / allowed values | Required | Default | Description |
569
- | -------------------- | -------------------------------- | :------: | --------- | --------------------------------------------- |
570
- | `renewUrl` | Absolute `http:` or `https:` URL | ✅ | — | Trusted private API renewal endpoint. |
571
- | `cookie.sessionName` | Valid cookie name | ❌ | `"__ses"` | Session cookie read and forwarded to the API. |
572
- | `cookie.renewName` | Valid cookie name | ❌ | `"__ren"` | Renewal scheduling cookie read by Next.js. |
687
+ | Property | Type / allowed values | Required | Default | Description |
688
+ | -------------------- | -------------------------------- | :------: | --------- | -------------------------------------------------------- |
689
+ | `renewUrl` | Absolute `http:` or `https:` URL | ✅ | — | Trusted private API renewal endpoint. |
690
+ | `cookie.sessionName` | Valid cookie name | ❌ | `"__ses"` | Session cookie read and forwarded to the API. |
691
+ | `cookie.contextName` | Valid cookie name | ❌ | `"__ctx"` | Signed context decoded only to schedule SSR renewal. |
573
692
 
574
693
  ## Session flow
575
694
 
@@ -582,8 +701,8 @@ credentials accepted
582
701
  -> load current account and owning user
583
702
  -> apply configured account/user access rules
584
703
  -> write __ses
585
- -> write __ren
586
- -> optionally write __cac
704
+ -> write signed __ctx with renewal schedule
705
+ -> optionally include short cached data inside __ctx
587
706
  ```
588
707
 
589
708
  Only the raw browser token authenticates. The database stores only its SHA-256 hash.
@@ -591,12 +710,16 @@ Only the raw browser token authenticates. The database stores only its SHA-256 h
591
710
  ### Protected `GET` or `HEAD`
592
711
 
593
712
  ```text
594
- session token
595
- -> valid signed cache?
596
- -> yes: expose cached account and session
597
- -> no: validate through DB and create a fresh cache
713
+ __ses + __ctx
714
+ -> renewal due?
715
+ -> yes: validate and renew through DB, then write both cookies
716
+ -> no: valid signed cache?
717
+ -> yes: expose cached account and session
718
+ -> no: validate through DB and write a fresh signed context
598
719
  ```
599
720
 
721
+ Renewal happens inside the same protected API request. A client-side fetch does not need a second renewal request.
722
+
600
723
  ### Unsafe request
601
724
 
602
725
  ```text
@@ -604,22 +727,22 @@ session token
604
727
  -> SHA-256 hash
605
728
  -> indexed DB lookup
606
729
  -> validate expiry, revocation, account/user access, and client
607
- -> clear short cache
730
+ -> write signed context without cached account data
608
731
  -> continue
609
732
  ```
610
733
 
611
- ### Renewal
734
+ ### Next.js SSR renewal
612
735
 
613
736
  ```text
614
- Next reads __ren
615
- -> future timestamp: no API request
737
+ Next decodes renew and exp from __ctx as untrusted hints
738
+ -> future timestamps: no renewal request
616
739
  -> missing, invalid, or due: POST /auth/renew
617
740
  -> API validates through DB
618
741
  -> update expires_at when renewal is due
619
- -> Set-Cookie with the same token, new renewAt, and fresh cache
742
+ -> Set-Cookie with the same token and fresh signed context
620
743
  ```
621
744
 
622
- `auth.session.resolve()` is always DB-backed and read-only. Only explicit renewal updates database expiry.
745
+ `auth.session.resolve()` is always DB-backed and read-only. `auth.session.renew()` performs the authoritative renewal. Framework `requireSession` calls it automatically when the verified context says renewal is due; the explicit endpoint gives Next.js the same behavior during SSR navigation.
623
746
 
624
747
  ## Hono adapter
625
748
 
@@ -657,7 +780,7 @@ type Session = {
657
780
  };
658
781
  ```
659
782
 
660
- The Prisma adapter refines `account` and `user` with the exact additional scalar fields declared in their respective `select` arrays.
783
+ The Prisma and Drizzle adapters refine `account` and `user` with the exact additional scalar fields declared in their respective `select` arrays.
661
784
 
662
785
  Only `account_id` is copied into the session row. Current account/user data is loaded through the fixed database relations and is never duplicated in the session table.
663
786
 
@@ -673,14 +796,14 @@ await auth.createSession({
673
796
  });
674
797
  ```
675
798
 
676
- `country` is normalized to uppercase and is never used for authentication or client matching. It is not resolved by the package and does not trigger GeoIP work during normal requests. When supplied, the configured session model must provide a nullable `country` column. When omitted, the Prisma adapter does not send the field.
799
+ `country` is normalized to uppercase and is never used for authentication or client matching. It is not resolved by the package and does not trigger GeoIP work during normal requests. When supplied, the configured session model must provide a nullable `country` column. When omitted, the database adapter does not send the field.
677
800
 
678
801
  ### Methods
679
802
 
680
803
  | Method | Purpose |
681
804
  | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------- |
682
805
  | `auth.createSession({ account_id, context, country? })` | Creates the DB session and writes the browser cookies. `country` is optional login-time metadata. |
683
- | `auth.resolveSession(context)` | Resolves a request and returns the selected account and session. |
806
+ | `auth.resolveSession(context)` | Resolves a request, renews inline when due, and returns the selected account and session. |
684
807
  | `auth.renewSession(context)` | Performs DB validation, renews when due, and writes authoritative cookies. |
685
808
  | `auth.revokeSession(context)` | Revokes the current DB session and clears cookies. |
686
809
  | `auth.clearSession(context)` | Clears browser cookies without revoking the DB session. |
@@ -689,11 +812,69 @@ await auth.createSession({
689
812
 
690
813
  `requireSession` authenticates only. Application-specific route permissions remain the application's responsibility.
691
814
 
815
+ ## Express adapter
816
+
817
+ ```ts
818
+ import { createExpressAuth, type ExpressAuthLocals } from "@gauts/auth/express";
819
+
820
+ const auth = createExpressAuth({ db, secret: process.env.AUTH_SECRET! });
821
+
822
+ app.get("/account", auth.requireSession, (_request, response) => {
823
+ const { account, session, user } = response.locals as ExpressAuthLocals;
824
+ response.json({ account, session, user });
825
+ });
826
+ ```
827
+
828
+ | Method | Purpose |
829
+ | ----------------------------------------------------------------- | ---------------------------------------------------- |
830
+ | `auth.createSession({ account_id, request, response, country? })` | Creates the DB session and writes cookies. |
831
+ | `auth.resolveSession({ request, response })` | Resolves and automatically renews when due. |
832
+ | `auth.renewSession({ request, response })` | Renews when due and writes authoritative cookies. |
833
+ | `auth.revokeSession({ request, response })` | Revokes the current session and clears cookies. |
834
+ | `auth.clearSession(response)` | Clears cookies without revoking the DB session. |
835
+ | `auth.getToken(request)` | Reads and validates the opaque session token. |
836
+ | `auth.requireSession` | Express middleware that populates `response.locals`. |
837
+
838
+ Express 5 forwards rejected async middleware promises to the application's error handler. The adapter does not install routes or an error handler.
839
+
840
+ ## Fastify adapter
841
+
842
+ Register the request decorators once before declaring routes:
843
+
844
+ ```ts
845
+ import { createFastifyAuth } from "@gauts/auth/fastify";
846
+
847
+ const auth = createFastifyAuth({ db, secret: process.env.AUTH_SECRET! });
848
+
849
+ auth.decorate(app);
850
+
851
+ app.get("/account", { preHandler: auth.requireSession }, (request) => ({
852
+ account: request.getDecorator("account"),
853
+ session: request.getDecorator("session"),
854
+ user: request.getDecorator("user"),
855
+ }));
856
+ ```
857
+
858
+ | Method | Purpose |
859
+ | -------------------------------------------------------------- | -------------------------------------------------------------- |
860
+ | `auth.decorate(app)` | Declares the native request decorators once at startup. |
861
+ | `auth.createSession({ account_id, request, reply, country? })` | Creates the DB session and writes cookies. |
862
+ | `auth.resolveSession({ request, reply })` | Resolves and automatically renews when due. |
863
+ | `auth.renewSession({ request, reply })` | Renews when due and writes authoritative cookies. |
864
+ | `auth.revokeSession({ request, reply })` | Revokes the current session and clears cookies. |
865
+ | `auth.clearSession(reply)` | Clears cookies without revoking the DB session. |
866
+ | `auth.getToken(request)` | Reads and validates the opaque session token. |
867
+ | `auth.requireSession` | Fastify `preHandler` that populates native request decorators. |
868
+
869
+ When social authentication is enabled, `auth.decorate(app)` also declares `social`. The adapter does not register a Fastify plugin or create routes.
870
+
692
871
  ## Social authentication
693
872
 
694
873
  Social authentication is an optional addon and remains disabled unless `social` is configured. Complete these steps only in applications that need Google, GitHub, or X authentication.
695
874
 
696
- ### 1. Add the social Prisma schema
875
+ ### 1. Add social persistence
876
+
877
+ #### Prisma
697
878
 
698
879
  Add the social relation inside the existing `user_accounts` model in the API schema:
699
880
 
@@ -745,6 +926,49 @@ const db = createPrismaAdapter({
745
926
  });
746
927
  ```
747
928
 
929
+ #### Drizzle MySQL/MariaDB
930
+
931
+ Add the provider table to the API schema:
932
+
933
+ ```ts
934
+ export const socialAccounts = mysqlTable(
935
+ "social_accounts",
936
+ {
937
+ account_id: varchar("account_id", { length: 255 })
938
+ .notNull()
939
+ .references(() => userAccounts.id, { onDelete: "cascade" }),
940
+ created_at: timestamp("created_at", { mode: "date" }).defaultNow().notNull(),
941
+ id: varchar("id", { length: 255 }).primaryKey(),
942
+ provider: varchar("provider", { length: 32 }).notNull(),
943
+ provider_id: varchar("provider_id", { length: 255 }).notNull(),
944
+ },
945
+ (table) => [
946
+ index("social_accounts_account_id_idx").on(table.account_id),
947
+ uniqueIndex("social_accounts_provider_provider_id_key").on(
948
+ table.provider,
949
+ table.provider_id,
950
+ ),
951
+ uniqueIndex("social_accounts_account_id_provider_key").on(table.account_id, table.provider),
952
+ ],
953
+ );
954
+ ```
955
+
956
+ Then supply it to the adapter:
957
+
958
+ ```ts
959
+ const db = createDrizzleAdapter({
960
+ client,
961
+ models: {
962
+ accounts: { table: userAccounts },
963
+ sessions: { table: accountSessions },
964
+ socials: { table: socialAccounts },
965
+ users: { table: users },
966
+ },
967
+ });
968
+ ```
969
+
970
+ Omitting `models.socials` keeps the Drizzle adapter session-only. The adapter creates the owning user and account in one transaction during default social registration, so that path requires a Drizzle MySQL driver with transaction support. Additional required user/account columns must have database defaults; otherwise the application must provide `registration.createAccount`.
971
+
748
972
  ### 2. Configure the providers
749
973
 
750
974
  Import only the providers used by the API:
@@ -783,7 +1007,7 @@ Register every `callbackUrl` shown above in the corresponding provider dashboard
783
1007
 
784
1008
  ### 3. Add the API route and frontend start
785
1009
 
786
- Declare one dynamic API route covering every configured provider. `social.handle` is Hono middleware: it completes `start` and expected error responses itself, then calls the application handler only after a successful callback.
1010
+ Declare one dynamic API route covering every configured provider. `social.handle` completes `start` and expected error responses itself, then continues to the application handler only after a successful callback.
787
1011
 
788
1012
  ```ts
789
1013
  app.get("/auth/social/:provider/:action", auth.social.handle, async (c) => {
@@ -827,16 +1051,28 @@ social.registered;
827
1051
  social.returnTo;
828
1052
  ```
829
1053
 
1054
+ The native equivalents are:
1055
+
1056
+ ```ts
1057
+ // Express
1058
+ const social = (response.locals as ExpressSocialLocals).social;
1059
+
1060
+ // Fastify
1061
+ const social = request.getDecorator<SocialAuthenticated>("social");
1062
+ ```
1063
+
1064
+ See the advanced framework examples for complete social routes and custom registration.
1065
+
830
1066
  The package does not create the application route, session, notifications, logs, or final success response. Those remain explicit in the route handler.
831
1067
 
832
1068
  ### Social configuration
833
1069
 
834
- | Property | Type / allowed values | Required | Default | Description |
835
- | ---------------------------- | ------------------------------------ | :---------: | --------- | -------------------------------------------------------------------------- |
836
- | `social.providers` | `SocialProvider[]` | | — | Configured Google, GitHub, or X providers. |
837
- | `social.cookieName` | Valid cookie name | | `"__soc"` | Signed temporary OAuth/registration transaction cookie. |
838
- | `social.registration` | `SocialRegistrationConfig` | | Disabled | Enables default or application-specific social registration. |
839
- | `registration.createAccount` | Async callback returning `accountId` | | Built-in | Creates required business data when the default structure is insufficient. |
1070
+ | Property | Type / allowed values | Required | Default | Description |
1071
+ | ---------------------------- | ------------------------------------ | :------: | --------- | -------------------------------------------------------------------------- |
1072
+ | `social.providers` | `SocialProvider[]` | | — | Configured Google, GitHub, or X providers. |
1073
+ | `social.cookieName` | Valid cookie name | | `"__soc"` | Signed temporary OAuth/registration transaction cookie. |
1074
+ | `social.registration` | `SocialRegistrationConfig` | | Disabled | Enables default or application-specific social registration. |
1075
+ | `registration.createAccount` | Async callback returning `accountId` | | Built-in | Creates required business data when the default structure is insufficient. |
840
1076
 
841
1077
  Provider configuration:
842
1078
 
@@ -952,7 +1188,7 @@ app.post("/auth/register/social", async (c) => {
952
1188
 
953
1189
  ### Core and adapter composition
954
1190
 
955
- `createHonoAuth()` is the normal entry point. Use separate composition only when the same core instance is required outside Hono:
1191
+ Use the entry point matching the application framework: `createHonoAuth()`, `createExpressAuth()`, or `createFastifyAuth()`. Use separate composition only when the same core instance is required outside the framework adapter:
956
1192
 
957
1193
  ```ts
958
1194
  import { createAuth } from "@gauts/auth";
@@ -961,12 +1197,13 @@ import { createHonoAdapter } from "@gauts/auth/hono";
961
1197
  const core = createAuth({ db });
962
1198
  const hono = createHonoAdapter({
963
1199
  auth: core,
1200
+ secret: process.env.AUTH_SECRET!,
964
1201
  });
965
1202
  ```
966
1203
 
967
1204
  ## Next.js adapter
968
1205
 
969
- The Next.js adapter schedules renewal; it does not authenticate pages or API requests.
1206
+ The Next.js adapter schedules SSR renewal; it does not authenticate pages or API requests. Normal API middleware independently verifies `__ctx` and renews inline, including requests made directly by client-side SWR or `fetch`.
970
1207
 
971
1208
  ```ts
972
1209
  import type { NextRequest } from "next/server";
@@ -992,13 +1229,13 @@ Result values:
992
1229
 
993
1230
  | `attempted` | `status` | Meaning |
994
1231
  | :---------: | ----------: | --------------------------------------------------------------- |
995
- | `false` | `null` | Session token exists and renewal is not due. |
1232
+ | `false` | `null` | Session token and context schedule exist; renewal is not due. |
996
1233
  | `false` | `401` | Session token is missing or malformed; no API request occurred. |
997
1234
  | `true` | HTTP status | The renewal endpoint was called and returned this status. |
998
1235
 
999
1236
  `unauthorizedUrl` is optional. When provided, the adapter redirects a `401` to that relative or absolute URL and transfers every returned `Set-Cookie` header to the redirect. Without it, the original response is returned with `status: 401` as before.
1000
1237
 
1001
- The adapter forwards only the session cookie and controlled client/origin headers required by the private API. Other cookies, authorization headers, and arbitrary headers are not forwarded.
1238
+ The adapter forwards only the session cookie and controlled client/origin headers required by the private API. `__ctx` is not forwarded because the API renewal endpoint always validates the opaque token through the database and returns a new signed context. Other cookies, authorization headers, and arbitrary headers are not forwarded.
1002
1239
 
1003
1240
  `buildForwardHeaders()` and `FORWARD_HEADERS` are exported from `@gauts/auth/next` for application fetchers that need the same controlled forwarding rules:
1004
1241
 
@@ -1015,7 +1252,7 @@ Safe client and proxy metadata is copied by default. Credentials such as `cookie
1015
1252
 
1016
1253
  When `Origin` is absent and trusted `X-Forwarded-Proto` and `X-Forwarded-Host` headers exist, the adapter reconstructs the public origin from them. It never derives a public origin from the internal Next.js request URL. The deployment proxy must overwrite forwarded headers received from untrusted clients.
1017
1254
 
1018
- Apply renewal only to protected routes or skip public routes before calling `nextAuth.renew()`.
1255
+ Apply renewal only to protected routes or skip public routes before calling `nextAuth.renew()`. The adapter calls the API only when `__ctx` is missing, malformed, expired, or its decoded renewal time is due. Those decoded dates are untrusted scheduling hints; they never authenticate the request.
1019
1256
 
1020
1257
  ## Core session API
1021
1258
 
@@ -1074,15 +1311,15 @@ const socialDb = {
1074
1311
  } satisfies SocialDbAdapter;
1075
1312
  ```
1076
1313
 
1077
- The Prisma adapter already implements both `DbAdapter` and `SocialDbAdapter`.
1314
+ The Prisma and Drizzle adapters implement both `DbAdapter` and `SocialDbAdapter` when their optional social model is available.
1078
1315
 
1079
1316
  ## Performance
1080
1317
 
1081
1318
  The package contains no Redis or in-process cache.
1082
1319
 
1083
- Without the optional browser cache, each `requireSession` performs an indexed database lookup through `account_sessions.token_hash`.
1320
+ Without the optional browser cache, each `requireSession` performs an indexed database lookup through `account_sessions.token_hash`. When renewal is due, that same request also updates the authoritative expiry and returns refreshed cookies; there is no additional client-side API call.
1084
1321
 
1085
- With a valid cache, `GET` and `HEAD` skip the lookup until `cache.ttl` expires. Unsafe methods always use current database state.
1322
+ With valid cached data inside `__ctx`, `GET` and `HEAD` skip the lookup until `cache.ttl` expires. Unsafe methods always use current database state. Renewal still validates through the database even if cached data is present.
1086
1323
 
1087
1324
  The tradeoff is explicit: revocation and selected account or relation changes made elsewhere may remain visible to safe cached requests until the short TTL expires. A 60-second TTL limits this stale-read window to one minute. Disable cache when immediate read revocation is required.
1088
1325
 
@@ -1134,7 +1371,6 @@ The package provides authentication primitives, not a complete application secur
1134
1371
  - TLS and trusted-proxy configuration;
1135
1372
  - CSRF, CORS, host, and origin validation;
1136
1373
  - login and renewal rate limiting;
1137
- - equivalent password verification work for unknown accounts;
1138
1374
  - route roles and authorization;
1139
1375
  - re-authentication for sensitive operations;
1140
1376
  - database migrations and session cleanup;