@passkeykit/server 2.0.0 → 2.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,247 @@
1
+ # @passkeykit/server
2
+
3
+ Server-side WebAuthn passkey verification — **stateless by default**. Works on Vercel, Cloudflare Workers, and traditional servers. Zero native dependencies.
4
+
5
+ Handles challenge generation, attestation/assertion verification, and includes scrypt password hashing (pure JS). Optional argon2 support via subpath export.
6
+
7
+ [![npm](https://img.shields.io/npm/v/@passkeykit/server)](https://www.npmjs.com/package/@passkeykit/server)
8
+ [![license](https://img.shields.io/npm/l/@passkeykit/server)](https://github.com/dnldev/passkey-kit/blob/main/LICENSE)
9
+
10
+ ## Install
11
+
12
+ ```bash
13
+ npm install @passkeykit/server
14
+ ```
15
+
16
+ ## Quick Start
17
+
18
+ ### Stateless (Serverless / Vercel / Cloudflare)
19
+
20
+ No database needed for challenges — they're encrypted into signed tokens.
21
+
22
+ ```typescript
23
+ import { PasskeyServer, FileCredentialStore } from '@passkeykit/server';
24
+ import { createExpressRoutes } from '@passkeykit/server/express';
25
+
26
+ const server = new PasskeyServer({
27
+ rpName: 'My App',
28
+ rpId: 'myapp.example.com',
29
+ allowedOrigins: ['https://myapp.example.com'],
30
+ encryptionKey: process.env.PASSKEY_SECRET!, // 32+ char secret
31
+ credentialStore: new FileCredentialStore('./data/credentials.json'),
32
+ });
33
+
34
+ // Mount ready-made Express routes
35
+ app.use('/api/auth/passkey', createExpressRoutes(server, {
36
+ getUserInfo: async (userId) => {
37
+ const user = await db.getUser(userId);
38
+ return user ? { id: user.id, name: user.name } : null;
39
+ },
40
+ onAuthenticationSuccess: async (userId) => {
41
+ return { token: generateSessionToken() };
42
+ },
43
+ }));
44
+ ```
45
+
46
+ ### Stateful (Traditional Server)
47
+
48
+ Use a challenge store if you need server-side challenge revocation.
49
+
50
+ ```typescript
51
+ import { PasskeyServer, MemoryChallengeStore, FileCredentialStore } from '@passkeykit/server';
52
+
53
+ const server = new PasskeyServer({
54
+ rpName: 'My App',
55
+ rpId: 'myapp.example.com',
56
+ allowedOrigins: ['https://myapp.example.com'],
57
+ challengeStore: new MemoryChallengeStore(),
58
+ credentialStore: new FileCredentialStore('./data/credentials.json'),
59
+ });
60
+ ```
61
+
62
+ ### Direct API (without Express)
63
+
64
+ ```typescript
65
+ // Registration
66
+ const regOptions = await server.generateRegistrationOptions(userId, userName);
67
+ // → send regOptions to client, client runs WebAuthn ceremony
68
+ const regResult = await server.verifyRegistration(attestationResponse, challengeToken);
69
+
70
+ // Authentication
71
+ const authOptions = await server.generateAuthenticationOptions();
72
+ // → send authOptions + sessionKey to client
73
+ const authResult = await server.verifyAuthentication(assertionResponse, sessionKey);
74
+ ```
75
+
76
+ ## Architecture
77
+
78
+ ```
79
+ Client Server
80
+ │ │
81
+ │── POST /register/options ──────▶│ Generate challenge
82
+ │◀── { options, challengeToken } ──│ Seal into AES-256-GCM token
83
+ │ │
84
+ │── WebAuthn ceremony (browser) ──│
85
+ │ │
86
+ │── POST /register/verify ───────▶│ Open token, verify attestation
87
+ │ { response, challengeToken } │ No DB lookup needed
88
+ │◀── { verified: true } ──────────│
89
+ ```
90
+
91
+ In **stateless mode**, the `challengeToken` is an encrypted, signed, expiring token. The server needs only the secret key — zero state.
92
+
93
+ In **stateful mode**, challenges are stored in your `ChallengeStore` and consumed on verification.
94
+
95
+ ## Express Routes
96
+
97
+ Mount a complete passkey API with one line:
98
+
99
+ ```typescript
100
+ import { createExpressRoutes } from '@passkeykit/server/express';
101
+
102
+ const routes = createExpressRoutes(server, {
103
+ getUserInfo: async (userId) => ({ id: userId, name: 'User' }),
104
+ onRegistrationSuccess: async (userId, credentialId) => {
105
+ console.log(`User ${userId} registered passkey ${credentialId}`);
106
+ },
107
+ onAuthenticationSuccess: async (userId) => {
108
+ return { sessionToken: createSession(userId) };
109
+ },
110
+ });
111
+
112
+ app.use('/api/auth/passkey', routes);
113
+ ```
114
+
115
+ **Routes created:**
116
+
117
+ | Method | Path | Description |
118
+ |--------|------|-------------|
119
+ | POST | `/register/options` | Get registration options + challenge |
120
+ | POST | `/register/verify` | Verify attestation response |
121
+ | POST | `/authenticate/options` | Get authentication options + challenge |
122
+ | POST | `/authenticate/verify` | Verify assertion response |
123
+ | GET | `/credentials/:userId` | List user's credentials |
124
+ | DELETE | `/credentials/:credentialId` | Delete a credential |
125
+
126
+ ## Password Hashing
127
+
128
+ Built-in scrypt hashing — pure JS, works everywhere (no native bindings):
129
+
130
+ ```typescript
131
+ import { hashPassword, verifyPassword, needsRehash } from '@passkeykit/server';
132
+
133
+ const hash = await hashPassword('my-passphrase');
134
+ // → $scrypt$ln=17,r=8,p=1$<salt>$<hash>
135
+
136
+ const valid = await verifyPassword(hash, 'my-passphrase'); // true
137
+
138
+ // Check if params have been upgraded since this hash was created
139
+ if (needsRehash(hash)) {
140
+ const newHash = await hashPassword('my-passphrase');
141
+ await db.updateHash(userId, newHash);
142
+ }
143
+ ```
144
+
145
+ ### argon2 (optional)
146
+
147
+ For native argon2id hashing, install `argon2` as a peer dependency:
148
+
149
+ ```bash
150
+ npm install argon2
151
+ ```
152
+
153
+ ```typescript
154
+ import { hashPassword, verifyPassword } from '@passkeykit/server/argon2';
155
+
156
+ const hash = await hashPassword('my-passphrase');
157
+ // → $argon2id$v=19$m=65536,t=3,p=4$...
158
+ ```
159
+
160
+ ## Storage Backends
161
+
162
+ ### Built-in Stores
163
+
164
+ | Store | Use Case |
165
+ |-------|----------|
166
+ | `MemoryChallengeStore` | Development / testing |
167
+ | `MemoryCredentialStore` | Development / testing |
168
+ | `FileChallengeStore` | Single-server deployments |
169
+ | `FileCredentialStore` | Single-server deployments |
170
+
171
+ ### Custom Stores
172
+
173
+ Implement the `ChallengeStore` and/or `CredentialStore` interfaces for your backend:
174
+
175
+ ```typescript
176
+ import type { CredentialStore, StoredCredential } from '@passkeykit/server';
177
+
178
+ class FirestoreCredentialStore implements CredentialStore {
179
+ async save(credential: StoredCredential) { /* ... */ }
180
+ async getByUserId(userId: string) { /* ... */ }
181
+ async getByCredentialId(credentialId: string) { /* ... */ }
182
+ async updateCounter(credentialId: string, newCounter: number) { /* ... */ }
183
+ async delete(credentialId: string) { /* ... */ }
184
+ }
185
+ ```
186
+
187
+ ```typescript
188
+ import type { ChallengeStore, StoredChallenge } from '@passkeykit/server';
189
+
190
+ class RedisChallengeStore implements ChallengeStore {
191
+ async save(key: string, challenge: StoredChallenge) { /* ... */ }
192
+ async consume(key: string) { /* ... */ }
193
+ }
194
+ ```
195
+
196
+ In **stateless mode**, you don't need a `ChallengeStore` at all — just set `encryptionKey`.
197
+
198
+ ## Configuration
199
+
200
+ ```typescript
201
+ interface PasskeyServerConfig {
202
+ rpName: string; // Shown to users during WebAuthn ceremony
203
+ rpId: string; // Must match the domain (e.g. 'example.com')
204
+ allowedOrigins: string[]; // e.g. ['https://example.com']
205
+ credentialStore: CredentialStore;
206
+
207
+ // Stateless mode (default — pick one):
208
+ encryptionKey?: string; // 32+ char secret for AES-256-GCM challenge tokens
209
+
210
+ // Stateful mode (alternative):
211
+ challengeStore?: ChallengeStore;
212
+
213
+ // Optional:
214
+ challengeTTL?: number; // Challenge expiry in ms (default: 5 minutes)
215
+ }
216
+ ```
217
+
218
+ ## Exports
219
+
220
+ | Import Path | Contents |
221
+ |-------------|----------|
222
+ | `@passkeykit/server` | `PasskeyServer`, stores, password hashing, types |
223
+ | `@passkeykit/server/express` | `createExpressRoutes()` — ready-made Express router |
224
+ | `@passkeykit/server/argon2` | `hashPassword()`, `verifyPassword()` — native argon2id |
225
+
226
+ ## Client Pairing
227
+
228
+ Use [`@passkeykit/client`](https://www.npmjs.com/package/@passkeykit/client) for the browser side. It handles the WebAuthn ceremony and `challengeToken` round-tripping automatically.
229
+
230
+ ```typescript
231
+ import { PasskeyClient } from '@passkeykit/client';
232
+
233
+ const client = new PasskeyClient({ serverUrl: '/api/auth/passkey' });
234
+ await client.register(userId, 'My Device');
235
+ await client.authenticate();
236
+ ```
237
+
238
+ ## Testing
239
+
240
+ ```bash
241
+ npm test
242
+ npm run test:coverage
243
+ ```
244
+
245
+ ## License
246
+
247
+ MIT — [GitHub](https://github.com/dnldev/passkey-kit)
package/dist/esm/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * passkey-kit-server
2
+ * @passkeykit/server
3
3
  *
4
4
  * Server-side WebAuthn passkey verification with challenge-response pattern
5
5
  * and scrypt password hashing (pure JS, works everywhere).
@@ -5,7 +5,7 @@
5
5
  * 1. Run on a platform with native module support (Node.js, not serverless edge)
6
6
  * 2. Want the absolute strongest password hash (argon2id > scrypt)
7
7
  *
8
- * Import: import { hashPassword, verifyPassword } from 'passkey-kit-server/argon2'
8
+ * Import: import { hashPassword, verifyPassword } from '@passkeykit/server/argon2'
9
9
  *
10
10
  * Most users should use the default scrypt export which works everywhere.
11
11
  */
@@ -6,7 +6,7 @@
6
6
  * Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge, browser.
7
7
  *
8
8
  * For users who want argon2id (requires native bindings), see the
9
- * `passkey-kit-server/argon2` subpath export.
9
+ * `@passkeykit/server/argon2` subpath export.
10
10
  *
11
11
  * Output format is PHC-like:
12
12
  * $scrypt$ln=17,r=8,p=1$<base64salt>$<base64hash>
package/dist/esm/types.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Type definitions for passkey-kit-server
2
+ * Type definitions for @passkeykit/server
3
3
  *
4
4
  * @ai_context These types define the storage interface abstraction.
5
5
  * Apps provide their own ChallengeStore and CredentialStore implementations
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * passkey-kit-server
2
+ * @passkeykit/server
3
3
  *
4
4
  * Server-side WebAuthn passkey verification with challenge-response pattern
5
5
  * and scrypt password hashing (pure JS, works everywhere).
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * passkey-kit-server
3
+ * @passkeykit/server
4
4
  *
5
5
  * Server-side WebAuthn passkey verification with challenge-response pattern
6
6
  * and scrypt password hashing (pure JS, works everywhere).
@@ -5,7 +5,7 @@
5
5
  * 1. Run on a platform with native module support (Node.js, not serverless edge)
6
6
  * 2. Want the absolute strongest password hash (argon2id > scrypt)
7
7
  *
8
- * Import: import { hashPassword, verifyPassword } from 'passkey-kit-server/argon2'
8
+ * Import: import { hashPassword, verifyPassword } from '@passkeykit/server/argon2'
9
9
  *
10
10
  * Most users should use the default scrypt export which works everywhere.
11
11
  */
@@ -6,7 +6,7 @@
6
6
  * 1. Run on a platform with native module support (Node.js, not serverless edge)
7
7
  * 2. Want the absolute strongest password hash (argon2id > scrypt)
8
8
  *
9
- * Import: import { hashPassword, verifyPassword } from 'passkey-kit-server/argon2'
9
+ * Import: import { hashPassword, verifyPassword } from '@passkeykit/server/argon2'
10
10
  *
11
11
  * Most users should use the default scrypt export which works everywhere.
12
12
  */
@@ -6,7 +6,7 @@
6
6
  * Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge, browser.
7
7
  *
8
8
  * For users who want argon2id (requires native bindings), see the
9
- * `passkey-kit-server/argon2` subpath export.
9
+ * `@passkeykit/server/argon2` subpath export.
10
10
  *
11
11
  * Output format is PHC-like:
12
12
  * $scrypt$ln=17,r=8,p=1$<base64salt>$<base64hash>
package/dist/password.js CHANGED
@@ -7,7 +7,7 @@
7
7
  * Node.js, Deno, Bun, Cloudflare Workers, Vercel Edge, browser.
8
8
  *
9
9
  * For users who want argon2id (requires native bindings), see the
10
- * `passkey-kit-server/argon2` subpath export.
10
+ * `@passkeykit/server/argon2` subpath export.
11
11
  *
12
12
  * Output format is PHC-like:
13
13
  * $scrypt$ln=17,r=8,p=1$<base64salt>$<base64hash>
package/dist/types.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Type definitions for passkey-kit-server
2
+ * Type definitions for @passkeykit/server
3
3
  *
4
4
  * @ai_context These types define the storage interface abstraction.
5
5
  * Apps provide their own ChallengeStore and CredentialStore implementations
package/dist/types.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /**
3
- * Type definitions for passkey-kit-server
3
+ * Type definitions for @passkeykit/server
4
4
  *
5
5
  * @ai_context These types define the storage interface abstraction.
6
6
  * Apps provide their own ChallengeStore and CredentialStore implementations
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@passkeykit/server",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Server-side WebAuthn passkey verification \u2014 stateless or stateful, pure JS, works on serverless",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/esm/index.js",
@@ -23,8 +23,18 @@
23
23
  }
24
24
  },
25
25
  "files": [
26
- "dist"
26
+ "dist",
27
+ "README.md"
27
28
  ],
29
+ "repository": {
30
+ "type": "git",
31
+ "url": "https://github.com/dnldev/passkey-kit.git",
32
+ "directory": "packages/server"
33
+ },
34
+ "homepage": "https://github.com/dnldev/passkey-kit/tree/main/packages/server#readme",
35
+ "bugs": {
36
+ "url": "https://github.com/dnldev/passkey-kit/issues"
37
+ },
28
38
  "scripts": {
29
39
  "build": "tsc && tsc -p tsconfig.esm.json && echo '{\"type\":\"module\"}' > dist/esm/package.json",
30
40
  "prepublishOnly": "npm run build"