bro-auth 0.1.0 → 0.1.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.
- package/LICENSE +21 -0
- package/README.md +433 -18
- package/package.json +16 -17
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Vaishnav
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,33 +1,448 @@
|
|
|
1
|
+
# bro-auth
|
|
2
|
+
|
|
3
|
+
```
|
|
1
4
|
┌──────────────────────────────────────────────────────────────┐
|
|
2
|
-
│ █▄▄
|
|
3
|
-
│ █▄█
|
|
5
|
+
│ █▄▄ █▀█ █▀█ ▄▀█ █░█ ▀█▀ █░█ bro-auth │
|
|
6
|
+
│ █▄█ █▀▄ █▄█ █▀█ █▀█ ░█░ █▀█ │
|
|
4
7
|
├──────────────────────────────────────────────────────────────┤
|
|
5
8
|
│ Stateless JWT · Device Fingerprinting · Zero Replay │
|
|
6
9
|
└──────────────────────────────────────────────────────────────┘
|
|
10
|
+
```
|
|
7
11
|
|
|
8
|
-
|
|
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
|
|
14
|
+
[](https://www.npmjs.com/package/bro-auth)
|
|
15
|
+
[](https://opensource.org/licenses/MIT)
|
|
17
16
|
|
|
18
17
|
---
|
|
19
18
|
|
|
20
|
-
##
|
|
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**
|
|
24
|
-
- 🚫 **Replay attack protection**
|
|
25
|
-
- ⚡ Lightweight
|
|
26
|
-
- 🧩 Works with
|
|
27
|
-
- 🌐 Browser module
|
|
28
|
-
- 📦
|
|
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 © Vaishnav
|
|
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.
|
|
3
|
+
"version": "0.1.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",
|
|
7
7
|
"module": "dist/index.mjs",
|
|
8
8
|
"browser": "dist/browser.mjs",
|
|
9
9
|
"exports": {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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",
|