bro-auth 0.2.1 → 0.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +378 -268
  2. package/package.json +1 -1
package/README.md CHANGED
@@ -1,174 +1,178 @@
1
+
1
2
  # bro-auth
2
3
 
3
- **Stateless JWT authentication with device fingerprint binding and derived signing keys.**
4
+ ```
5
+ ╔══════════════════════════════════════════════════════════════╗
6
+ ║ █▄▄ █▀█ █▀█ █▀█ █ █ ▀█▀ █ █ ║
7
+ ║ █▄█ █▀▄ █▄█ █▀█ █▄█ █ █▀█ ║
8
+ ╠══════════════════════════════════════════════════════════════╣
9
+ ║ Stateless JWT · Device Fingerprinting ║
10
+ ╚══════════════════════════════════════════════════════════════╝
11
+ ```
12
+
13
+ **Stateless JWT authentication with browser fingerprint binding and derived signing keys.**
14
+
4
15
 
5
16
  [![npm version](https://img.shields.io/npm/v/bro-auth.svg)](https://www.npmjs.com/package/bro-auth)
6
17
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
7
-
8
18
  ---
9
19
 
10
20
  ## Overview
11
21
 
12
- `bro-auth` is a Node.js library that binds JWT tokens to browser devices using fingerprinting and derives signing keys from application-provided secrets. This prevents token replay attacks and limits the impact of token theft.
22
+ bro-auth is a stateless authentication library that strengthens JWT security by binding tokens to a browser/device fingerprint and deriving signing keys per user + device.
13
23
 
14
- **What bro-auth does:**
15
- - Generates and verifies JWT tokens bound to device fingerprints
16
- - Derives unique signing secrets per user-device combination using HMAC
17
- - Provides browser fingerprinting utilities
24
+ It is designed to:
18
25
 
19
- **What bro-auth does NOT do:**
20
- - Manage secrets or environment variables (your application's responsibility)
21
- - Prevent XSS attacks (your application's responsibility)
22
- - Handle login UI or session management (your application's responsibility)
26
+ * Prevent token-only theft
27
+ * Block non-browser and cross-device replay
28
+ * Keep authentication stateless and scalable
29
+ * Be plug-and-play for developers
23
30
 
31
+ > **bro-auth strengthens stateless JWT authentication by binding tokens to browser context.**
32
+ > It is designed to make token theft, cross-device reuse, and blind replay attacks significantly harder — while remaining fully stateless and developer-friendly.
24
33
  ---
25
34
 
26
- ## Why Use bro-auth?
35
+ ## Why bro-auth?
27
36
 
28
- Traditional JWT tokens work from any device if stolen. `bro-auth` binds tokens to specific devices, making stolen tokens unusable on different browsers.
37
+ JWT-based authentication is simple and scalable but **by default, JWTs are bearer tokens**.
38
+ If a token is stolen, it can often be reused from anywhere.
29
39
 
30
- ### Attack Mitigation
40
+ **bro-auth raises the security bar without adding server-side state.**
31
41
 
32
- For an attacker to successfully use a stolen token, they would need:
42
+ It does this by:
43
+ - Binding tokens to the browser/device that created them
44
+ - Deriving signing keys per user + device context
45
+ - Making token-only theft insufficient for reuse
46
+ - Preventing cross-browser and non-browser replay
47
+ - Keeping the system fully stateless and horizontally scalable
33
48
 
34
- 1. The JWT token itself (stolen via XSS, network interception, etc.)
35
- 2. The user ID (embedded in token)
36
- 3. The application's access/refresh secrets
37
- 4. The exact device fingerprint hash (SHA-256)
38
- 5. The application's server-only pepper (`BRO_AUTH_SECRET_PEPPER`)
49
+ bro-auth is ideal when you want:
50
+ - Better security than plain JWT
51
+ - Zero session storage
52
+ - Simple developer experience
53
+ - Clear, well-defined security trade-offs
39
54
 
40
- Even with items 1-4, the attacker cannot:
41
- - Use the token from a different device (fingerprint mismatch fails verification)
42
- - Forge new tokens (requires the server-only pepper)
43
- - Replay the token (derived secret verification fails)
44
55
 
45
- ### Security Scope & Limitations
56
+ ---
46
57
 
47
- **What bro-auth protects against:**
48
- - Token replay from different devices
49
- - Token forgery without server secrets
50
- - Credential stuffing across devices
58
+ ## Threat Model (Important – Read This)
51
59
 
52
- **What bro-auth does NOT protect against:**
53
- - XSS attacks during active execution (attacker can make requests while JS runs)
54
- - Full server compromise (if secrets are leaked, all bets are off)
55
- - Social engineering or phishing attacks
56
- - Browser fingerprint spoofing (though difficult, it's theoretically possible)
60
+ ### bro-auth protects against:
57
61
 
58
- **Your application must:**
59
- - Implement XSS prevention (CSP, input sanitization, etc.)
60
- - Store tokens securely (HTTP-only cookies for refresh tokens)
61
- - Protect environment variables and secrets
62
- - Use HTTPS in production
62
+ * JWT copied from logs, storage, or memory
63
+ * Token reuse from a different browser/device
64
+ * Non-browser attacks (curl, Postman, bots)
65
+ * Token swapping across users
66
+ * Blind replay without browser context
63
67
 
64
- ---
68
+ ### bro-auth does NOT protect against:
65
69
 
66
- ## Installation
70
+ * XSS with active JS execution
71
+ * Malicious browser extensions
72
+ * Compromised dependencies running in-browser
73
+ * Replay after fingerprint observation
74
+ * Full device compromise
67
75
 
68
- ```bash
69
- npm install bro-auth
70
- ```
76
+ > **This is a hard limit of stateless authentication, not a bug.**
71
77
 
72
78
  ---
73
79
 
74
- ## How It Works
80
+ ## High-Level Design
81
+
82
+ ### Core idea
83
+
84
+ A JWT is only valid if the same browser fingerprint that created it is presented again.
75
85
 
76
- ### 1. Authentication Flow
86
+ ### Authentication Flow
87
+
88
+ #### 1. Login
77
89
 
78
90
  ```
79
- Browser Server
91
+ Browser Server
80
92
 
81
- ├─ Generate fingerprint hash
82
- │ (Canvas, GPU, User-Agent, etc.)
93
+ ├─ Generate RAW fingerprint
94
+ │ (Canvas, GPU, UA, timezone, etc.)
83
95
 
84
- ├─ POST /login ────────────────────────▶ Verify credentials
85
- │ { username, password, fpHash }
86
- ├─ Derive signing secret:
87
- HMAC(pepper, secret|userId|fpHash)
88
-
89
- ├─ Sign JWT with derived secret
90
- Payload: { sub: userId, fp: fpHash }
91
-
92
- ◀──────────────────────────────────── Return tokens
93
- │ { accessToken, refreshToken }
96
+ ├─ POST /login ─────────────────────▶ Verify credentials
97
+ │ { username, password, rawFP }
98
+ ├─ Normalize & hash fingerprint:
99
+ fpHash = SHA256(rawFP)
100
+
101
+ ├─ Derive signing secret:
102
+ HMAC(pepper, secret|userId|fpHash)
103
+
104
+ ├─ Sign JWT
105
+ Payload: { sub, fp: fpHash }
94
106
 
107
+ ◀──────────────────────────────────── Return tokens
95
108
  ```
96
109
 
97
- ### 2. Verification Flow
110
+ #### 2. Protected API Request
98
111
 
99
112
  ```
100
- Browser Server
101
-
102
- ├─ GET /api/protected ─────────────────▶ Extract token & fpHash
103
- │ Headers: │
104
- │ Authorization: Bearer <token> ├─ Decode token (unsafe)
105
- │ X-Fingerprint: <fpHash> │ Extract userId from payload
106
- │ │
107
- │ ├─ Re-derive signing secret:
108
- │ │ HMAC(pepper, secret|userId|fpHash)
109
- │ │
110
- │ ├─ Verify JWT signature
111
- │ │ jwt.verify(token, derivedSecret)
112
- │ │
113
- │ ├─ Compare fingerprints
114
- │ │ payload.fp === request.fpHash
115
- │ │
116
- │ ◀──────────────────────────────────── Grant access ✓
113
+ Browser Server
117
114
 
115
+ ├─ GET /api/protected ──────────────▶
116
+ │ Authorization: Bearer <token> │
117
+ │ X-Fingerprint: <rawFP> │
118
+ │ │
119
+ │ ├─ Hash fingerprint again:
120
+ │ │ SHA256(rawFP)
121
+ │ │
122
+ │ ├─ Re-derive signing secret
123
+ │ │
124
+ │ ├─ Verify JWT signature
125
+ │ │
126
+ │ ├─ Compare fp hashes
127
+ │ │
128
+ ◀──────────────────────────────────── Access granted
118
129
  ```
119
130
 
120
- ### 3. Why Stolen Tokens Fail
131
+ ### Why Token-Only Theft Fails
121
132
 
122
- ```
123
- Attacker (Different Device) Server
124
-
125
- ├─ GET /api/protected ─────────────────▶ Extract token & fpHash
126
- │ Token: <stolen_token> │
127
- │ Fingerprint: <attacker_fp_hash> ├─ Decode token
128
- │ │ payload.fp = <victim_fp_hash>
129
- │ │
130
- │ ├─ Derive secret using attacker's FP:
131
- │ │ HMAC(pepper, secret|userId|attacker_fp)
132
- │ │
133
- │ ├─ Verify signature
134
- │ │ ✗ FAILS - Different derived secret
135
- │ │
136
- │ ◀──────────────────────────────────── Reject: "invalid signature"
137
-
138
- ```
133
+ If an attacker steals **only the JWT:**
139
134
 
140
- **Key insight:** The signing secret is derived from the fingerprint. A different fingerprint produces a different secret, causing signature verification to fail.
135
+ * They do not know the browser fingerprint
136
+ * Signature verification fails
137
+ * Token cannot be reused elsewhere
141
138
 
142
- ---
139
+ If the attacker also steals the raw fingerprint:
143
140
 
144
- ## Quick Start
141
+ * Replay may succeed until token expiry
142
+ * This is a known stateless limitation
145
143
 
146
- ### Step 1: Configure Environment Variables
144
+ ---
147
145
 
148
- Create a `.env` file in your application (NOT in bro-auth):
146
+ ## Installation
149
147
 
150
148
  ```bash
151
- # Required: Application-owned server-only pepper
152
- # bro-auth requires this but does NOT provide it
153
- BRO_AUTH_SECRET_PEPPER=your-random-string-min-32-chars-never-expose
149
+ npm install bro-auth
150
+ ```
151
+
152
+ ---
154
153
 
155
- # Your application's JWT signing secrets
156
- ACCESS_SECRET=your-access-secret-min-32-chars
157
- REFRESH_SECRET=your-refresh-secret-min-32-chars
154
+ ## Environment Variables
155
+
156
+ ```bash
157
+ BRO_AUTH_SECRET_PEPPER=long-random-server-only-value
158
+ ACCESS_SECRET=your-access-secret
159
+ REFRESH_SECRET=your-refresh-secret
158
160
  ```
159
161
 
160
- **Important:** These are YOUR application's secrets. `bro-auth` reads them but does not manage or provide them.
162
+ > ⚠️ **These secrets must never be exposed to the browser.**
163
+
164
+ ---
161
165
 
162
- ### Step 2: Browser - Generate Fingerprint
166
+ ## Browser Usage
167
+
168
+ ### Generate fingerprint (RAW)
163
169
 
164
170
  ```javascript
165
171
  import { getFingerprint } from "bro-auth/browser";
166
172
 
167
173
  async function handleLogin() {
168
174
  // Generate device fingerprint hash
169
- const fpHash = await getFingerprint();
170
-
171
- // fpHash is a string: "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
175
+ const rawFingerprint = await getFingerprint();
172
176
 
173
177
  const response = await fetch("/api/login", {
174
178
  method: "POST",
@@ -176,18 +180,27 @@ async function handleLogin() {
176
180
  body: JSON.stringify({
177
181
  username: "user@example.com",
178
182
  password: "password123",
179
- fingerprint: fpHash
183
+ fingerprint: rawFingerprint
180
184
  })
181
185
  });
182
186
 
183
187
  const { accessToken, refreshToken } = await response.json();
184
188
 
185
- // Store tokens (see security best practices)
189
+ // Store access token (see Token Storage Best Practices below)
186
190
  sessionStorage.setItem("accessToken", accessToken);
191
+
192
+ // Refresh token is typically set as HTTP-only cookie by the server
187
193
  }
188
194
  ```
189
195
 
190
- ### Step 3: Server - Generate Tokens
196
+ **Returns:** A normalized raw string
197
+ **Note:** Never hashed on the client, sent as-is to the backend
198
+
199
+ ---
200
+
201
+ ## Server Usage
202
+
203
+ ### Generate Tokens
191
204
 
192
205
  ```javascript
193
206
  import { generateTokens } from "bro-auth/core";
@@ -204,7 +217,7 @@ app.post("/api/login", async (req, res) => {
204
217
  // 2. Generate device-bound tokens
205
218
  const { accessToken, refreshToken } = generateTokens(
206
219
  user.id, // userId
207
- fingerprint, // fpHash from browser
220
+ fingerprint, // RAW fingerprint from browser
208
221
  process.env.ACCESS_SECRET, // your secret
209
222
  process.env.REFRESH_SECRET // your secret
210
223
  );
@@ -213,7 +226,9 @@ app.post("/api/login", async (req, res) => {
213
226
  });
214
227
  ```
215
228
 
216
- ### Step 4: Server - Verify Requests
229
+ > **Note:** bro-auth hashes the fingerprint internally using SHA-256.
230
+
231
+ ### Verify Access Token
217
232
 
218
233
  ```javascript
219
234
  import { verifyAccessToken } from "bro-auth/core";
@@ -228,7 +243,7 @@ app.get("/api/protected", (req, res) => {
228
243
 
229
244
  const result = verifyAccessToken(
230
245
  token,
231
- fingerprint,
246
+ fingerprint, // RAW fingerprint
232
247
  process.env.ACCESS_SECRET
233
248
  );
234
249
 
@@ -242,7 +257,37 @@ app.get("/api/protected", (req, res) => {
242
257
  });
243
258
  ```
244
259
 
245
- ### Step 5: Server - Refresh Tokens
260
+ ### Why RAW FP → Server-Side Hashing?
261
+
262
+ * Prevents trusting client-side hashes blindly
263
+ * Ensures consistent normalization
264
+ * Improves debuggability
265
+ * Avoids mismatch bugs
266
+ * Keeps hashing logic centralized
267
+
268
+ > **Hashing is not claimed as replay prevention** — it is a binding and consistency mechanism.
269
+
270
+ ---
271
+
272
+ ## API Reference (Key Functions)
273
+
274
+ ### `getFingerprint()`
275
+
276
+ Returns normalized raw fingerprint string.
277
+
278
+ ### `generateTokens(userId, rawFP, accessSecret, refreshSecret)`
279
+
280
+ * Hashes fingerprint internally
281
+ * Derives signing secrets
282
+ * Issues access + refresh tokens
283
+
284
+ ### `verifyAccessToken(token, rawFP, secret)`
285
+
286
+ * Hashes fingerprint again
287
+ * Re-derives signing key
288
+ * Verifies JWT integrity & binding
289
+
290
+ ### Refresh Tokens
246
291
 
247
292
  ```javascript
248
293
  import { verifyRefreshToken, generateTokens } from "bro-auth/core";
@@ -274,7 +319,154 @@ app.post("/api/refresh", (req, res) => {
274
319
 
275
320
  ---
276
321
 
277
- ## API Reference
322
+ ## Token Storage Best Practices
323
+
324
+ ### Access Token Storage
325
+
326
+ You can store access tokens in:
327
+
328
+ * **HTTP-only cookies** (Recommended) - Most secure, immune to XSS
329
+ * **Session storage** - Good for SPAs, cleared on tab close
330
+ * **Local storage** - Not recommended, vulnerable to XSS attacks
331
+ * **Memory (React state/Vue reactive)** - Secure but lost on page refresh
332
+
333
+ **Best practice:** Use HTTP-only cookies for maximum security.
334
+
335
+ #### Example: Storing Access Token in HTTP-only Cookie (Server-side)
336
+
337
+ ```javascript
338
+ import { generateTokens } from "bro-auth/core";
339
+
340
+ app.post("/api/login", async (req, res) => {
341
+ const { username, password, fingerprint } = req.body;
342
+ const user = await authenticateUser(username, password);
343
+
344
+ if (!user) {
345
+ return res.status(401).json({ error: "Invalid credentials" });
346
+ }
347
+
348
+ const { accessToken, refreshToken } = generateTokens(
349
+ user.id,
350
+ fingerprint,
351
+ process.env.ACCESS_SECRET,
352
+ process.env.REFRESH_SECRET
353
+ );
354
+
355
+ // Set access token as HTTP-only cookie
356
+ res.cookie("accessToken", accessToken, {
357
+ httpOnly: true,
358
+ secure: true,
359
+ sameSite: "strict",
360
+ maxAge: 15 * 60 * 1000 // 15 minutes
361
+ });
362
+
363
+ // Set refresh token as HTTP-only cookie
364
+ res.cookie("refreshToken", refreshToken, {
365
+ httpOnly: true,
366
+ secure: true,
367
+ sameSite: "strict",
368
+ maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
369
+ });
370
+
371
+ res.json({ success: true });
372
+ });
373
+ ```
374
+
375
+ ### Refresh Token Storage
376
+
377
+ **Always store refresh tokens in HTTP-only cookies.**
378
+
379
+ bro-auth provides a built-in helper method `buildRefreshCookie` to simplify this.
380
+
381
+ #### Using `buildRefreshCookie`
382
+
383
+ ```javascript
384
+ import { generateTokens, buildRefreshCookie } from "bro-auth/core";
385
+
386
+ app.post("/api/login", async (req, res) => {
387
+ const { username, password, fingerprint } = req.body;
388
+ const user = await authenticateUser(username, password);
389
+
390
+ if (!user) {
391
+ return res.status(401).json({ error: "Invalid credentials" });
392
+ }
393
+
394
+ const { accessToken, refreshToken } = generateTokens(
395
+ user.id,
396
+ fingerprint,
397
+ process.env.ACCESS_SECRET,
398
+ process.env.REFRESH_SECRET
399
+ );
400
+
401
+ // Use bro-auth's built-in helper for refresh token cookie
402
+ const refreshCookie = buildRefreshCookie(refreshToken);
403
+
404
+ res.cookie(
405
+ refreshCookie.name,
406
+ refreshCookie.value,
407
+ refreshCookie.options
408
+ );
409
+
410
+ // Return access token (client can store in sessionStorage or cookie)
411
+ res.json({ accessToken });
412
+ });
413
+ ```
414
+
415
+ #### Custom Configuration for `buildRefreshCookie`
416
+
417
+ ```javascript
418
+ import { buildRefreshCookie } from "bro-auth/core";
419
+
420
+ // Default: 7 days
421
+ const cookie = buildRefreshCookie(refreshToken);
422
+
423
+ // Custom expiry: 24 hours
424
+ const cookie24h = buildRefreshCookie(refreshToken, 60 * 60 * 24);
425
+
426
+ // The cookie object contains:
427
+ // {
428
+ // name: "bro_refresh",
429
+ // value: "<token>",
430
+ // options: {
431
+ // httpOnly: true,
432
+ // secure: true,
433
+ // sameSite: "strict",
434
+ // path: "/",
435
+ // maxAge: <seconds>
436
+ // }
437
+ // }
438
+ ```
439
+
440
+ #### Clearing Refresh Token on Logout
441
+
442
+ ```javascript
443
+ import { buildClearRefreshCookie } from "bro-auth/core";
444
+
445
+ app.post("/api/logout", (req, res) => {
446
+ const clearCookie = buildClearRefreshCookie();
447
+
448
+ res.cookie(
449
+ clearCookie.name,
450
+ clearCookie.value,
451
+ clearCookie.options
452
+ );
453
+
454
+ res.json({ message: "Logged out successfully" });
455
+ });
456
+ ```
457
+
458
+ ### Additional Security Best Practices
459
+
460
+ * Use short-lived access tokens (5–15 min)
461
+ * Enable CSP (Content Security Policy) to reduce XSS risk
462
+ * Rotate secrets on compromise
463
+ * Always use HTTPS in production
464
+ * Implement rate limiting on authentication endpoints
465
+ * Treat bro-auth as hardening, not magic
466
+
467
+ ---
468
+
469
+ ## Full API Reference
278
470
 
279
471
  ### Browser Module (`bro-auth/browser`)
280
472
 
@@ -411,43 +603,72 @@ Verifies a refresh token and fingerprint binding.
411
603
 
412
604
  ---
413
605
 
414
- #### `buildRefreshCookie(refreshToken, options?)`
606
+ #### `buildRefreshCookie(refreshToken, maxAge?)`
415
607
 
416
- Generates a secure HTTP-only cookie string for refresh tokens.
608
+ Generates a secure HTTP-only cookie configuration object for refresh tokens.
417
609
 
418
610
  **Parameters:**
419
611
  - `refreshToken` (string) - The refresh token
420
- - `options` (object, optional):
421
- - `maxAge` (number) - Cookie lifetime in seconds (default: 604800 = 7 days)
422
- - `domain` (string) - Cookie domain
423
- - `sameSite` ("Strict" | "Lax" | "None") - SameSite policy (default: "Strict")
424
- - `secure` (boolean) - HTTPS only (default: true)
612
+ - `maxAge` (number, optional) - Cookie lifetime in seconds (default: 604800 = 7 days)
425
613
 
426
- **Returns:** `string` (Set-Cookie header value)
614
+ **Returns:** `Object`
615
+ ```javascript
616
+ {
617
+ name: "bro_refresh",
618
+ value: string,
619
+ options: {
620
+ httpOnly: true,
621
+ secure: true,
622
+ sameSite: "strict",
623
+ path: "/",
624
+ maxAge: number
625
+ }
626
+ }
627
+ ```
427
628
 
428
629
  **Example:**
429
630
  ```javascript
430
- const cookie = buildRefreshCookie(refreshToken, {
431
- maxAge: 86400,
432
- sameSite: "Strict",
433
- secure: true
434
- });
631
+ import { buildRefreshCookie } from "bro-auth/core";
632
+
633
+ const refreshCookie = buildRefreshCookie(refreshToken);
634
+
635
+ // Express/Fastify
636
+ res.cookie(
637
+ refreshCookie.name,
638
+ refreshCookie.value,
639
+ refreshCookie.options
640
+ );
435
641
 
436
- res.setHeader("Set-Cookie", cookie);
642
+ // Next.js App Router
643
+ import { cookies } from "next/headers";
644
+ cookies().set(
645
+ refreshCookie.name,
646
+ refreshCookie.value,
647
+ refreshCookie.options
648
+ );
437
649
  ```
438
650
 
439
651
  ---
440
652
 
441
653
  #### `buildClearRefreshCookie()`
442
654
 
443
- Generates a cookie string to clear the refresh token (for logout).
655
+ Generates a cookie configuration object to clear the refresh token (for logout).
444
656
 
445
- **Returns:** `string`
657
+ **Returns:** `Object` (same structure as `buildRefreshCookie` but with empty value and maxAge: 0)
446
658
 
447
659
  **Example:**
448
660
  ```javascript
661
+ import { buildClearRefreshCookie } from "bro-auth/core";
662
+
449
663
  app.post("/api/logout", (req, res) => {
450
- res.setHeader("Set-Cookie", buildClearRefreshCookie());
664
+ const clearCookie = buildClearRefreshCookie();
665
+
666
+ res.cookie(
667
+ clearCookie.name,
668
+ clearCookie.value,
669
+ clearCookie.options
670
+ );
671
+
451
672
  res.json({ message: "Logged out" });
452
673
  });
453
674
  ```
@@ -469,101 +690,38 @@ Derives a unique signing secret using HMAC-SHA256 with the application's pepper.
469
690
 
470
691
  ---
471
692
 
472
- ## Security Best Practices
693
+ ## FAQ (Corrected)
473
694
 
474
- ### 1. Environment Variables
695
+ **"Is bro-auth replay-proof?"**
475
696
 
476
- Never hardcode secrets. Use environment variables:
697
+ No.
698
+ It blocks token-only replay, not replay after full browser compromise.
477
699
 
478
- ```bash
479
- # .env (add to .gitignore)
480
- BRO_AUTH_SECRET_PEPPER=min-32-chars-random-string
481
- ACCESS_SECRET=min-32-chars-random-string
482
- REFRESH_SECRET=min-32-chars-random-string
483
- ```
484
-
485
- Generate secrets using:
486
- ```bash
487
- node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"
488
- ```
700
+ **"Is this better than plain JWT?"**
489
701
 
490
- ### 2. Token Storage
702
+ Yes. Significantly.
491
703
 
492
- **Access tokens:**
493
- - Store in memory (React state, Vue reactive)
494
- - Session storage is acceptable for SPAs
495
- - **Never** in localStorage (XSS vulnerable)
704
+ **"Is this WebAuthn?"**
496
705
 
497
- **Refresh tokens:**
498
- - HTTP-only, Secure, SameSite=Strict cookies (recommended)
499
- - **Never** accessible to JavaScript
706
+ No.
707
+ bro-auth is stateless. WebAuthn is stateful + hardware-backed.
500
708
 
501
- **Example:**
502
- ```javascript
503
- // ✓ Good: In-memory
504
- const [accessToken, setAccessToken] = useState(null);
505
-
506
- // ✗ Bad: localStorage
507
- localStorage.setItem("token", accessToken); // XSS vulnerable
508
- ```
509
-
510
- ### 3. HTTPS Only
511
-
512
- Always use HTTPS in production:
513
-
514
- ```javascript
515
- const cookie = buildRefreshCookie(refreshToken, {
516
- secure: process.env.NODE_ENV === "production",
517
- sameSite: "Strict"
518
- });
519
- ```
520
-
521
- ### 4. Short-Lived Access Tokens
522
-
523
- Keep access token TTL short (5-15 minutes):
524
-
525
- ```javascript
526
- generateAccessToken(userId, fpHash, secret, "15m");
527
- ```
528
-
529
- ### 5. Token Rotation
530
-
531
- Rotate refresh tokens on each use:
532
-
533
- ```javascript
534
- app.post("/api/refresh", async (req, res) => {
535
- const result = verifyRefreshToken(/* ... */);
536
-
537
- if (result.valid) {
538
- // Issue new token pair
539
- const newTokens = generateTokens(/* ... */);
540
-
541
- // Optional: Invalidate old refresh token in database
542
- await revokeToken(req.body.refreshToken);
543
-
544
- res.json(newTokens);
545
- }
546
- });
547
- ```
709
+ ---
548
710
 
549
- ### 6. Rate Limiting
711
+ ## Final Positioning (Important)
550
712
 
551
- Implement rate limiting on auth endpoints:
713
+ **bro-auth is a stateless JWT hardening library.**
714
+ It raises the bar against real-world JWT misuse while remaining scalable and developer-friendly.
552
715
 
553
- ```javascript
554
- import rateLimit from "express-rate-limit";
716
+ That statement is:
555
717
 
556
- const loginLimiter = rateLimit({
557
- windowMs: 15 * 60 * 1000,
558
- max: 5
559
- });
560
-
561
- app.post("/api/login", loginLimiter, handleLogin);
562
- ```
718
+ * technically correct
719
+ * interview-safe
720
+ * production-honest
563
721
 
564
722
  ---
565
723
 
566
- ## Framework Examples
724
+ ## Advanced Framework Examples
567
725
 
568
726
  ### Express.js Middleware
569
727
 
@@ -719,55 +877,6 @@ export const useAuth = () => useContext(AuthContext);
719
877
 
720
878
  ---
721
879
 
722
- ## FAQ
723
-
724
- **Q: Can users have multiple devices?**
725
-
726
- A: Yes. Each device generates its own fingerprint. Issue separate token pairs for each device. Optionally track active sessions by storing fingerprint hashes.
727
-
728
- **Q: What if the fingerprint changes (browser update)?**
729
-
730
- A: The user must re-authenticate. This is intentional—it prevents fingerprint spoofing. For better UX, implement a "trusted devices" feature or send email notifications for new logins.
731
-
732
- **Q: What about privacy concerns?**
733
-
734
- A: The fingerprint is SHA-256 hashed before transmission. Only the hash is sent to the server. However, disclose fingerprinting in your privacy policy and comply with GDPR/CCPA.
735
-
736
- **Q: Does this work with mobile apps?**
737
-
738
- A: The browser module is web-only. For mobile apps, use native device identifiers:
739
- - iOS: `identifierForVendor`
740
- - Android: `ANDROID_ID`
741
- - React Native: `react-native-device-info`
742
-
743
- **Q: How do I invalidate tokens immediately?**
744
-
745
- A: `bro-auth` is stateless, so tokens can't be revoked until expiry. For immediate invalidation:
746
- - Maintain a token blacklist in Redis
747
- - Use short-lived access tokens (5-15 min)
748
- - Implement token versioning (increment on password change)
749
-
750
- **Q: Can I use this with GraphQL?**
751
-
752
- A: Yes. Pass credentials in HTTP headers:
753
-
754
- ```javascript
755
- const client = new ApolloClient({
756
- uri: '/graphql',
757
- request: async (operation) => {
758
- const fpHash = await getFingerprint();
759
- operation.setContext({
760
- headers: {
761
- authorization: `Bearer ${accessToken}`,
762
- 'x-fingerprint': fpHash
763
- }
764
- });
765
- }
766
- });
767
- ```
768
-
769
- ---
770
-
771
880
  ## Testing
772
881
 
773
882
  ### Run Backend Tests
@@ -811,7 +920,7 @@ Contributions welcome. Please:
811
920
 
812
921
  ## License
813
922
 
814
- MIT © Vaishnav
923
+ MIT © Vaishnav - Creator of bro-auth
815
924
 
816
925
  ---
817
926
 
@@ -819,4 +928,5 @@ MIT © Vaishnav
819
928
 
820
929
  - [NPM Package](https://www.npmjs.com/package/bro-auth)
821
930
  - [GitHub Repository](https://github.com/ChakraVaishnav/bro-auth)
822
- - [Report Issues](https://github.com/ChakraVaishnav/bro-auth/issues)
931
+ - [Report Issues](https://github.com/ChakraVaishnav/bro-auth/issues)
932
+ - [Portfolio](https://giyu.me)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bro-auth",
3
- "version": "0.2.1",
3
+ "version": "0.2.2",
4
4
  "description": "bro-auth — Stateless, fingerprint-bound JWT authentication. Server utilities + browser fingerprinting module.",
5
5
  "type": "module",
6
6
  "main": "dist/index.cjs",