bro-auth 0.1.0 → 0.1.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.
Files changed (2) hide show
  1. package/README.md +433 -18
  2. package/package.json +16 -17
package/README.md CHANGED
@@ -1,33 +1,448 @@
1
+ # bro-auth
2
+
3
+ ```
1
4
  ┌──────────────────────────────────────────────────────────────┐
2
- │ █▄▄█▀██▀█▄▀██░█▀█▀█░█ bro-auth │
3
- │ █▄██▀▄ █▄█   █▀██▀█░█░█▀█ │
5
+ │ █▄▄ █▀█ █▀█ ▄▀█ █░█ ▀█▀ █░█ bro-auth │
6
+ │ █▄█ █▀▄ █▄█ █▀█ █▀█ ░█░ █▀█ │
4
7
  ├──────────────────────────────────────────────────────────────┤
5
8
  │ Stateless JWT · Device Fingerprinting · Zero Replay │
6
9
  └──────────────────────────────────────────────────────────────┘
10
+ ```
7
11
 
8
- # bro-auth
9
- A lightweight, **stateless**, and **high-security** authentication layer using:
10
-
11
- ✅ JWT access tokens
12
- ✅ Refresh tokens
13
- ✅ Device fingerprint binding (prevents stolen-token replay)
14
- ✅ No database required
12
+ A lightweight, **stateless**, and **high-security** authentication layer that combines JWT tokens with device fingerprinting to prevent token theft and replay attacks—no database required.
15
13
 
16
- bro-auth aims to provide **DPoP-inspired protection** without the complexity.
14
+ [![npm version](https://img.shields.io/npm/v/bro-auth.svg)](https://www.npmjs.com/package/bro-auth)
15
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
17
16
 
18
17
  ---
19
18
 
20
- ## 🚀 Features
19
+ ## 🎯 Why bro-auth?
20
+
21
+ Traditional JWT authentication has a critical weakness: **stolen tokens work from any device**. If an attacker intercepts your JWT, they can use it from anywhere until it expires.
22
+
23
+ **bro-auth solves this** by binding tokens to specific devices using browser fingerprinting. Even if a token is stolen, it won't work on a different device.
24
+
25
+ ### Key Benefits
21
26
 
22
- - 🔐 **Stateless JWT authentication**
23
- - 🆔 **Device fingerprint binding** (SHA-256 hashed)
24
- - 🚫 **Replay attack protection** (tokens tied to a specific browser)
25
- - ⚡ Lightweight, zero dependencies except `jsonwebtoken` + `crypto-es`
26
- - 🧩 Works with ANY backend (Next.js, Express, Node HTTP)
27
- - 🌐 Browser module provided for fingerprint extraction
28
- - 📦 Ready for NPM consumption
27
+ - 🔐 **Stateless JWT authentication** - No session storage needed
28
+ - 🆔 **Device fingerprint binding** - Tokens only work on the issuing device
29
+ - 🚫 **Replay attack protection** - Stolen tokens are useless on other browsers
30
+ - ⚡ **Zero dependencies** - Lightweight core (only `jsonwebtoken` + `crypto-es`)
31
+ - 🧩 **Framework agnostic** - Works with Next.js, Express, Fastify, or vanilla Node
32
+ - 🌐 **Browser module included** - Easy fingerprint extraction
33
+ - 📦 **Production ready** - TypeScript support, comprehensive error handling
29
34
 
30
35
  ---
31
36
 
32
37
  ## 📦 Installation
33
38
 
39
+ ```bash
40
+ npm install bro-auth
41
+ ```
42
+
43
+ ```bash
44
+ yarn add bro-auth
45
+ ```
46
+
47
+ ```bash
48
+ pnpm add bro-auth
49
+ ```
50
+
51
+ ---
52
+
53
+ ## 🧠 How It Works
54
+
55
+ ```mermaid
56
+ sequenceDiagram
57
+ participant Browser
58
+ participant Server
59
+
60
+ Browser->>Browser: Generate device fingerprint
61
+ Browser->>Server: Login with credentials + fingerprint
62
+ Server->>Server: Hash fingerprint (SHA-256)
63
+ Server->>Server: Create JWT bound to fingerprint hash
64
+ Server->>Browser: Return access + refresh tokens
65
+
66
+ Browser->>Server: API request with token + fingerprint
67
+ Server->>Server: Verify token signature
68
+ Server->>Server: Verify fingerprint match
69
+ Server->>Browser: Grant access ✅
70
+
71
+ Note over Browser,Server: If attacker steals token...
72
+
73
+ Browser->>Server: Request from different device
74
+ Server->>Server: Fingerprint mismatch detected
75
+ Server->>Browser: Reject request ❌
76
+ ```
77
+
78
+ ### The Security Flow
79
+
80
+ 1. **Client generates a device fingerprint** using browser characteristics (User-Agent, screen, GPU, canvas, etc.)
81
+ 2. **Server receives fingerprint** during login and creates a SHA-256 hash
82
+ 3. **JWT tokens are bound** to this fingerprint hash in their payload
83
+ 4. **Every request is verified** for both token validity and fingerprint match
84
+ 5. **Token theft is mitigated** - stolen tokens fail fingerprint verification on different devices
85
+
86
+ ---
87
+
88
+ ## 🚀 Quick Start
89
+
90
+ ### 1. Browser: Generate Device Fingerprint
91
+
92
+ ```javascript
93
+ import { getFingerprint } from "bro-auth/browser";
94
+
95
+ async function handleLogin() {
96
+ const fp = await getFingerprint();
97
+
98
+ // Send to your login endpoint
99
+ const response = await fetch("/api/login", {
100
+ method: "POST",
101
+ headers: { "Content-Type": "application/json" },
102
+ body: JSON.stringify({
103
+ username: "user@example.com",
104
+ password: "password",
105
+ fingerprint: fp.hash
106
+ })
107
+ });
108
+
109
+ const { accessToken, refreshToken } = await response.json();
110
+ // Store tokens securely
111
+ }
112
+ ```
113
+
114
+ **Fingerprint output example:**
115
+
116
+ ```json
117
+ {
118
+ "hash": "53ff76d8a4c21b9c8e3f1a7d9e2c4b5a8f1e3d6c9b2a5e8f1d4c7b0a3e6f9c2696",
119
+ "raw": "Mozilla/5.0|1920x1080|ANGLE (Intel, Mesa Intel UHD)|canvas_data...",
120
+ "components": {
121
+ "userAgent": "Mozilla/5.0 (X11; Linux x86_64)...",
122
+ "screenResolution": "1920x1080",
123
+ "gpu": "ANGLE (Intel, Mesa Intel UHD Graphics 620)",
124
+ "canvas": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg...",
125
+ "timezone": "America/New_York"
126
+ }
127
+ }
128
+ ```
129
+
130
+ ### 2. Server: Generate Tokens
131
+
132
+ ```javascript
133
+ import { generateTokens } from "bro-auth";
134
+
135
+ app.post("/api/login", async (req, res) => {
136
+ const { username, password, fingerprint } = req.body;
137
+
138
+ // Verify credentials (your logic here)
139
+ const user = await verifyCredentials(username, password);
140
+ if (!user) {
141
+ return res.status(401).json({ error: "Invalid credentials" });
142
+ }
143
+
144
+ // Generate tokens bound to device fingerprint
145
+ const { accessToken, refreshToken } = generateTokens({
146
+ userId: user.id,
147
+ fingerprintHash: fingerprint,
148
+ accessSecret: process.env.ACCESS_SECRET,
149
+ refreshSecret: process.env.REFRESH_SECRET,
150
+ accessExpiresIn: "15m", // Optional: default 15m
151
+ refreshExpiresIn: "7d" // Optional: default 7d
152
+ });
153
+
154
+ res.json({ accessToken, refreshToken });
155
+ });
156
+ ```
157
+
158
+ ### 3. Server: Verify Access Token
159
+
160
+ ```javascript
161
+ import { verifyAccessToken } from "bro-auth";
162
+
163
+ app.get("/api/protected", (req, res) => {
164
+ const token = req.headers.authorization?.replace("Bearer ", "");
165
+ const fingerprint = req.headers["x-fingerprint"];
166
+
167
+ const result = verifyAccessToken(
168
+ token,
169
+ fingerprint,
170
+ process.env.ACCESS_SECRET
171
+ );
172
+
173
+ if (!result.valid) {
174
+ return res.status(401).json({ error: result.error });
175
+ }
176
+
177
+ // Access granted
178
+ const userId = result.payload.userId;
179
+ res.json({ message: "Protected data", userId });
180
+ });
181
+ ```
182
+
183
+ ### 4. Server: Refresh Tokens
184
+
185
+ ```javascript
186
+ import { verifyRefreshToken, generateTokens, buildRefreshCookie } from "bro-auth";
187
+
188
+ app.post("/api/refresh", (req, res) => {
189
+ const { refreshToken, fingerprint } = req.body;
190
+
191
+ const result = verifyRefreshToken(
192
+ refreshToken,
193
+ fingerprint,
194
+ process.env.REFRESH_SECRET
195
+ );
196
+
197
+ if (!result.valid) {
198
+ return res.status(401).json({ error: result.error });
199
+ }
200
+
201
+ // Issue new token pair
202
+ const tokens = generateTokens({
203
+ userId: result.payload.userId,
204
+ fingerprintHash: fingerprint,
205
+ accessSecret: process.env.ACCESS_SECRET,
206
+ refreshSecret: process.env.REFRESH_SECRET
207
+ });
208
+
209
+ // Optional: Set refresh token as HTTP-only cookie
210
+ const cookie = buildRefreshCookie(tokens.refreshToken);
211
+ res.setHeader("Set-Cookie", cookie);
212
+
213
+ res.json({ accessToken: tokens.accessToken });
214
+ });
215
+ ```
216
+
217
+ ---
218
+
219
+ ## 📚 API Reference
220
+
221
+ ### Browser Module
222
+
223
+ #### `getFingerprint(): Promise<FingerprintResult>`
224
+
225
+ Generates a unique device fingerprint from browser characteristics.
226
+
227
+ **Returns:**
228
+ ```typescript
229
+ {
230
+ hash: string; // SHA-256 hash (send to server)
231
+ raw: string; // Raw concatenated fingerprint data
232
+ components: { // Individual fingerprint components
233
+ userAgent: string;
234
+ screenResolution: string;
235
+ gpu: string;
236
+ canvas: string;
237
+ timezone: string;
238
+ // ... more components
239
+ }
240
+ }
241
+ ```
242
+
243
+ ### Server Module
244
+
245
+ #### `generateTokens(options): TokenPair`
246
+
247
+ Creates JWT access and refresh tokens bound to a device fingerprint.
248
+
249
+ **Parameters:**
250
+ - `userId` (string): Unique user identifier
251
+ - `fingerprintHash` (string): Device fingerprint hash from browser
252
+ - `accessSecret` (string): Secret key for access tokens
253
+ - `refreshSecret` (string): Secret key for refresh tokens
254
+ - `accessExpiresIn` (string, optional): Access token TTL (default: "15m")
255
+ - `refreshExpiresIn` (string, optional): Refresh token TTL (default: "7d")
256
+
257
+ **Returns:**
258
+ ```typescript
259
+ {
260
+ accessToken: string;
261
+ refreshToken: string;
262
+ }
263
+ ```
264
+
265
+ #### `verifyAccessToken(token, fingerprintHash, secret): VerificationResult`
266
+
267
+ Verifies an access token and its fingerprint binding.
268
+
269
+ **Returns:**
270
+ ```typescript
271
+ {
272
+ valid: boolean;
273
+ payload?: {
274
+ userId: string;
275
+ fingerprintHash: string;
276
+ iat: number;
277
+ exp: number;
278
+ };
279
+ error?: string;
280
+ }
281
+ ```
282
+
283
+ #### `verifyRefreshToken(token, fingerprintHash, secret): VerificationResult`
284
+
285
+ Verifies a refresh token and its fingerprint binding.
286
+
287
+ #### `buildRefreshCookie(refreshToken, options?): string`
288
+
289
+ Generates a secure HTTP-only cookie string for refresh tokens.
290
+
291
+ **Options:**
292
+ - `maxAge` (number): Cookie lifetime in seconds (default: 7 days)
293
+ - `domain` (string, optional): Cookie domain
294
+ - `sameSite` ("Strict" | "Lax" | "None"): SameSite policy (default: "Strict")
295
+ - `secure` (boolean): HTTPS only (default: true)
296
+
297
+ ---
298
+
299
+ ## 🔒 Security Best Practices
300
+
301
+ ### 1. Environment Variables
302
+
303
+ Never hardcode secrets. Use environment variables:
304
+
305
+ ```bash
306
+ # .env
307
+ ACCESS_SECRET=your-super-secret-access-key-min-32-chars
308
+ REFRESH_SECRET=your-super-secret-refresh-key-min-32-chars
309
+ ```
310
+
311
+ ### 2. Token Storage
312
+
313
+ **Access tokens:**
314
+ - Store in memory (React state, Vue reactive)
315
+ - Never in localStorage (XSS vulnerable)
316
+
317
+ **Refresh tokens:**
318
+ - Use HTTP-only cookies (best)
319
+ - Or secure memory storage with HTTPS
320
+
321
+ ### 3. HTTPS Only
322
+
323
+ Always use HTTPS in production to prevent man-in-the-middle attacks.
324
+
325
+ ### 4. Short-lived Access Tokens
326
+
327
+ Keep access token TTL short (5-15 minutes) to limit exposure window.
328
+
329
+ ### 5. Token Rotation
330
+
331
+ Rotate refresh tokens on each use to detect token theft:
332
+
333
+ ```javascript
334
+ // When refreshing, invalidate old refresh token
335
+ // and issue a new pair
336
+ ```
337
+
338
+ ---
339
+
340
+ ## 🎨 Integration Examples
341
+
342
+ ### Next.js App Router
343
+
344
+ ```typescript
345
+ // app/api/login/route.ts
346
+ import { generateTokens } from "bro-auth";
347
+ import { NextRequest, NextResponse } from "next/server";
348
+
349
+ export async function POST(req: NextRequest) {
350
+ const { username, password, fingerprint } = await req.json();
351
+
352
+ // Your auth logic
353
+ const user = await authenticate(username, password);
354
+
355
+ const tokens = generateTokens({
356
+ userId: user.id,
357
+ fingerprintHash: fingerprint,
358
+ accessSecret: process.env.ACCESS_SECRET!,
359
+ refreshSecret: process.env.REFRESH_SECRET!
360
+ });
361
+
362
+ return NextResponse.json(tokens);
363
+ }
364
+ ```
365
+
366
+ ### Express.js Middleware
367
+
368
+ ```javascript
369
+ import { verifyAccessToken } from "bro-auth";
370
+
371
+ export const authMiddleware = (req, res, next) => {
372
+ const token = req.headers.authorization?.replace("Bearer ", "");
373
+ const fingerprint = req.headers["x-fingerprint"];
374
+
375
+ const result = verifyAccessToken(
376
+ token,
377
+ fingerprint,
378
+ process.env.ACCESS_SECRET
379
+ );
380
+
381
+ if (!result.valid) {
382
+ return res.status(401).json({ error: "Unauthorized" });
383
+ }
384
+
385
+ req.userId = result.payload.userId;
386
+ next();
387
+ };
388
+
389
+ // Usage
390
+ app.get("/api/user", authMiddleware, (req, res) => {
391
+ res.json({ userId: req.userId });
392
+ });
393
+ ```
394
+
395
+ ---
396
+
397
+ ## ❓ FAQ
398
+
399
+ ### Q: Can users have multiple devices?
400
+
401
+ **A:** Yes! Each device generates its own fingerprint. Issue separate token pairs for each device. You can track active sessions by storing fingerprint hashes (optional).
402
+
403
+ ### Q: What if fingerprint changes (browser update, etc.)?
404
+
405
+ **A:** The user will need to re-authenticate. This is a security feature—it prevents fingerprint spoofing. Consider implementing a "trusted devices" system for better UX.
406
+
407
+ ### Q: Is this more secure than sessions?
408
+
409
+ **A:** It's different. Sessions require server-side storage but can be invalidated instantly. bro-auth is stateless (scales better) but tokens can't be revoked until expiry. Choose based on your needs.
410
+
411
+ ### Q: What about privacy?
412
+
413
+ **A:** The fingerprint is hashed with SHA-256 before storage. Only the hash is sent to the server—individual components stay client-side. However, browser fingerprinting can be privacy-sensitive, so disclose this in your privacy policy.
414
+
415
+ ### Q: Does this work with mobile apps?
416
+
417
+ **A:** The browser module is web-only. For mobile apps, generate a device ID using native APIs (iOS: `identifierForVendor`, Android: `ANDROID_ID`) and use that as the fingerprint.
418
+
419
+ ---
420
+
421
+ ## 🤝 Contributing
422
+
423
+ Contributions are welcome! Please feel free to submit a Pull Request.
424
+
425
+ 1. Fork the repository
426
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
427
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
428
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
429
+ 5. Open a Pull Request
430
+
431
+ ---
432
+
433
+ ## 📄 License
434
+
435
+ MIT © [Your Name]
436
+
437
+ ---
438
+
439
+ ---
440
+
441
+ ## 🔗 Links
442
+
443
+ - [NPM Package](https://www.npmjs.com/package/bro-auth)
444
+ - [GitHub Repository](https://github.com/ChakraVaishnav/bro-auth)
445
+
446
+ ---
447
+
448
+ **Made with 💪 by developers who care about security**
package/package.json CHANGED
@@ -1,35 +1,34 @@
1
1
  {
2
2
  "name": "bro-auth",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
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",
7
7
  "module": "dist/index.mjs",
8
8
  "browser": "dist/browser.mjs",
9
9
  "exports": {
10
- ".": {
11
- "import": "./dist/index.js",
12
- "require": "./dist/index.cjs"
10
+ ".": {
11
+ "import": "./dist/index.js",
12
+ "require": "./dist/index.cjs"
13
+ },
14
+ "./browser": {
15
+ "import": "./dist/browser.js",
16
+ "require": "./dist/browser.cjs"
17
+ }
13
18
  },
14
- "./browser": {
15
- "import": "./dist/browser.js",
16
- "require": "./dist/browser.cjs"
17
- }
18
- }
19
- ,
20
19
  "files": [
21
20
  "dist/",
22
21
  "README.md",
23
22
  "LICENSE"
24
23
  ],
25
24
  "scripts": {
26
- "clean": "rimraf dist",
27
- "build:server": "tsup src/core/index.js --format cjs,esm --no-dts --out-dir dist",
28
- "build:browser": "tsup src/browser/index.js --format cjs,esm --no-dts --out-dir dist --platform browser",
29
- "build": "npm run clean && npm run build:server && npm run build:browser",
30
- "prepack": "npm run build",
31
- "test": "node --input-type=module -e \"import * as core from './dist/index.js'; console.log('core exports:', Object.keys(core));\""
32
- },
25
+ "clean": "rimraf dist",
26
+ "build:server": "tsup src/core/index.js --format cjs,esm --no-dts --out-dir dist",
27
+ "build:browser": "tsup src/browser/index.js --format cjs,esm --no-dts --out-dir dist --platform browser",
28
+ "build": "npm run clean && npm run build:server && npm run build:browser",
29
+ "prepack": "npm run build",
30
+ "test": "node --input-type=module -e \"import * as core from './dist/index.js'; console.log('core exports:', Object.keys(core));\""
31
+ },
33
32
  "keywords": [
34
33
  "jwt",
35
34
  "authentication",