@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.
- package/README.md +376 -140
- package/SECURITY.md +8 -4
- package/dist/adapters/drizzle/config.d.ts +11 -0
- package/dist/adapters/drizzle/config.d.ts.map +1 -0
- package/dist/adapters/drizzle/config.js +147 -0
- package/dist/adapters/drizzle/config.js.map +1 -0
- package/dist/adapters/drizzle/index.d.ts +4 -0
- package/dist/adapters/drizzle/index.d.ts.map +1 -0
- package/dist/adapters/drizzle/index.js +156 -0
- package/dist/adapters/drizzle/index.js.map +1 -0
- package/dist/adapters/drizzle/model.d.ts +21 -0
- package/dist/adapters/drizzle/model.d.ts.map +1 -0
- package/dist/adapters/drizzle/model.js +61 -0
- package/dist/adapters/drizzle/model.js.map +1 -0
- package/dist/adapters/drizzle/schema.d.ts +311 -0
- package/dist/adapters/drizzle/schema.d.ts.map +1 -0
- package/dist/adapters/drizzle/schema.js +37 -0
- package/dist/adapters/drizzle/schema.js.map +1 -0
- package/dist/adapters/drizzle/types.d.ts +109 -0
- package/dist/adapters/drizzle/types.d.ts.map +1 -0
- package/dist/adapters/drizzle/types.js +2 -0
- package/dist/adapters/drizzle/types.js.map +1 -0
- package/dist/adapters/express/index.d.ts +77 -0
- package/dist/adapters/express/index.d.ts.map +1 -0
- package/dist/adapters/express/index.js +72 -0
- package/dist/adapters/express/index.js.map +1 -0
- package/dist/adapters/express/social.d.ts +29 -0
- package/dist/adapters/express/social.d.ts.map +1 -0
- package/dist/adapters/express/social.js +44 -0
- package/dist/adapters/express/social.js.map +1 -0
- package/dist/adapters/fastify/index.d.ts +79 -0
- package/dist/adapters/fastify/index.d.ts.map +1 -0
- package/dist/adapters/fastify/index.js +100 -0
- package/dist/adapters/fastify/index.js.map +1 -0
- package/dist/adapters/fastify/social.d.ts +27 -0
- package/dist/adapters/fastify/social.d.ts.map +1 -0
- package/dist/adapters/fastify/social.js +48 -0
- package/dist/adapters/fastify/social.js.map +1 -0
- package/dist/adapters/hono/index.d.ts +10 -23
- package/dist/adapters/hono/index.d.ts.map +1 -1
- package/dist/adapters/hono/index.js +32 -224
- package/dist/adapters/hono/index.js.map +1 -1
- package/dist/adapters/hono/social.d.ts +4 -10
- package/dist/adapters/hono/social.d.ts.map +1 -1
- package/dist/adapters/hono/social.js +25 -221
- package/dist/adapters/hono/social.js.map +1 -1
- package/dist/adapters/model.d.ts +33 -0
- package/dist/adapters/model.d.ts.map +1 -0
- package/dist/adapters/model.js +87 -0
- package/dist/adapters/model.js.map +1 -0
- package/dist/adapters/next/index.d.ts +1 -1
- package/dist/adapters/next/index.d.ts.map +1 -1
- package/dist/adapters/next/index.js +7 -3
- package/dist/adapters/next/index.js.map +1 -1
- package/dist/adapters/prisma/config.d.ts.map +1 -1
- package/dist/adapters/prisma/config.js +1 -4
- package/dist/adapters/prisma/config.js.map +1 -1
- package/dist/adapters/prisma/model.d.ts +7 -8
- package/dist/adapters/prisma/model.d.ts.map +1 -1
- package/dist/adapters/prisma/model.js +11 -79
- package/dist/adapters/prisma/model.js.map +1 -1
- package/dist/adapters/utils/cookie.d.ts +17 -0
- package/dist/adapters/utils/cookie.d.ts.map +1 -0
- package/dist/adapters/utils/cookie.js +59 -0
- package/dist/adapters/utils/cookie.js.map +1 -0
- package/dist/adapters/utils/session.d.ts +4 -0
- package/dist/adapters/utils/session.d.ts.map +1 -0
- package/dist/adapters/utils/session.js +208 -0
- package/dist/adapters/utils/session.js.map +1 -0
- package/dist/adapters/utils/social.d.ts +51 -0
- package/dist/adapters/utils/social.d.ts.map +1 -0
- package/dist/adapters/utils/social.js +278 -0
- package/dist/adapters/utils/social.js.map +1 -0
- package/dist/adapters/utils/types.d.ts +61 -0
- package/dist/adapters/utils/types.d.ts.map +1 -0
- package/dist/adapters/utils/types.js +2 -0
- package/dist/adapters/utils/types.js.map +1 -0
- package/dist/config.d.ts +1 -2
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +1 -2
- package/dist/config.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/session/cookie.d.ts +6 -8
- package/dist/session/cookie.d.ts.map +1 -1
- package/dist/session/cookie.js +7 -24
- package/dist/session/cookie.js.map +1 -1
- package/dist/session/state.d.ts +41 -0
- package/dist/session/state.d.ts.map +1 -0
- package/dist/session/state.js +192 -0
- package/dist/session/state.js.map +1 -0
- package/package.json +40 -3
- package/src/adapters/drizzle/schema.ts +47 -0
- package/src/adapters/prisma/schema.prisma +38 -0
- package/dist/session/cache.d.ts +0 -32
- package/dist/session/cache.d.ts.map +0 -1
- package/dist/session/cache.js +0 -138
- 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
|
-
|
|
|
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,
|
|
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
|
-
|
|
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
|
|
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
|
|
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`,
|
|
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
|
-
|
|
62
|
+
Express TypeScript projects also install its type declarations:
|
|
60
63
|
|
|
61
|
-
|
|
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
|
-
|
|
85
|
+
Social authentication is an optional addon with separate schema instructions later in this README.
|
|
70
86
|
|
|
71
|
-
|
|
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
|
-
|
|
77
|
-
}
|
|
89
|
+
Install Prisma inside the API project:
|
|
78
90
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
password_hash String? @db.VarChar(255)
|
|
91
|
+
```bash
|
|
92
|
+
npm install @prisma/client
|
|
93
|
+
npm install --save-dev prisma
|
|
94
|
+
```
|
|
84
95
|
|
|
85
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
107
|
+
```ts
|
|
108
|
+
import { createPrismaAdapter } from "@gauts/auth/prisma";
|
|
105
109
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
+
import { prisma } from "./db.js";
|
|
111
|
+
|
|
112
|
+
export const authDb = createPrismaAdapter({
|
|
113
|
+
client: prisma,
|
|
114
|
+
});
|
|
110
115
|
```
|
|
111
116
|
|
|
112
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
-
|
|
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 {
|
|
161
|
+
import { authDb } from "./authDb.js";
|
|
128
162
|
|
|
129
163
|
export const auth = createHonoAuth({
|
|
130
|
-
db:
|
|
131
|
-
|
|
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,
|
|
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`;
|
|
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` |
|
|
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
|
-
| `
|
|
370
|
-
| `
|
|
371
|
-
| `
|
|
372
|
-
| `
|
|
373
|
-
| `
|
|
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
|
-
|
|
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.
|
|
397
|
-
auth.cookie.renewName; // "__ren"
|
|
445
|
+
auth.cookie.contextName; // "__ctx"
|
|
398
446
|
```
|
|
399
447
|
|
|
400
|
-
### Signed
|
|
448
|
+
### Signed session context
|
|
401
449
|
|
|
402
|
-
| Property | Type / allowed values | Default | Description
|
|
403
|
-
| ----------- | ----------------------------------- | -------- |
|
|
404
|
-
| `
|
|
405
|
-
| `
|
|
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
|
|
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
|
|
410
|
-
- is
|
|
411
|
-
-
|
|
412
|
-
-
|
|
413
|
-
-
|
|
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
|
|
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
|
|
468
|
+
<summary>Internal signed context payload</summary>
|
|
419
469
|
|
|
420
470
|
```ts
|
|
421
471
|
{
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
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 `
|
|
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.
|
|
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
|
|
586
|
-
-> optionally
|
|
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
|
-
|
|
595
|
-
->
|
|
596
|
-
-> yes:
|
|
597
|
-
-> no:
|
|
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
|
-
->
|
|
730
|
+
-> write signed context without cached account data
|
|
608
731
|
-> continue
|
|
609
732
|
```
|
|
610
733
|
|
|
611
|
-
###
|
|
734
|
+
### Next.js SSR renewal
|
|
612
735
|
|
|
613
736
|
```text
|
|
614
|
-
Next
|
|
615
|
-
-> future
|
|
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
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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`
|
|
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 |
|
|
835
|
-
| ---------------------------- | ------------------------------------ |
|
|
836
|
-
| `social.providers` | `SocialProvider[]` |
|
|
837
|
-
| `social.cookieName` | Valid cookie name |
|
|
838
|
-
| `social.registration` | `SocialRegistrationConfig` |
|
|
839
|
-
| `registration.createAccount` | Async callback returning `accountId` |
|
|
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()`
|
|
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
|
|
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
|
|
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
|
|
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;
|