@xivdyetools/auth 1.0.0 → 1.0.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/README.md ADDED
@@ -0,0 +1,158 @@
1
+ # @xivdyetools/auth
2
+
3
+ Shared authentication utilities for the xivdyetools ecosystem. Provides secure JWT verification, HMAC signing, timing-safe comparison, and Discord signature verification.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @xivdyetools/auth
9
+ ```
10
+
11
+ ## Features
12
+
13
+ - **JWT Verification** - HMAC-SHA256 JWT verification with algorithm validation
14
+ - **HMAC Signing** - Create and verify HMAC-SHA256 signatures
15
+ - **Timing-Safe Comparison** - Constant-time string comparison to prevent timing attacks
16
+ - **Discord Verification** - Ed25519 signature verification for Discord interactions
17
+ - **Tree-Shakeable** - Subpath exports for minimal bundle size
18
+
19
+ ## Usage
20
+
21
+ ### JWT Verification
22
+
23
+ ```typescript
24
+ import { verifyJWT, decodeJWT, isJWTExpired } from '@xivdyetools/auth';
25
+
26
+ // Verify JWT with signature and expiration checking
27
+ const payload = await verifyJWT(token, process.env.JWT_SECRET);
28
+ if (!payload) {
29
+ // Invalid signature, expired, or wrong algorithm
30
+ }
31
+
32
+ // Decode without verification (debugging only)
33
+ const decoded = decodeJWT(token);
34
+
35
+ // Check if JWT is expired
36
+ if (isJWTExpired(payload)) {
37
+ // Token has expired
38
+ }
39
+ ```
40
+
41
+ ### HMAC Signing
42
+
43
+ ```typescript
44
+ import { hmacSign, hmacVerify, verifyBotSignature } from '@xivdyetools/auth';
45
+
46
+ // Sign data with HMAC-SHA256 (base64url output)
47
+ const signature = await hmacSign(data, secret);
48
+
49
+ // Verify signature
50
+ const isValid = await hmacVerify(data, signature, secret);
51
+
52
+ // Verify bot request signature (with timestamp validation)
53
+ const isValidBot = await verifyBotSignature(
54
+ signature, // X-Request-Signature header
55
+ timestamp, // X-Request-Timestamp header
56
+ userDiscordId,
57
+ userName,
58
+ secret,
59
+ { maxAgeMs: 5 * 60 * 1000 } // Optional: 5 minute max age
60
+ );
61
+ ```
62
+
63
+ ### Timing-Safe Comparison
64
+
65
+ ```typescript
66
+ import { timingSafeEqual } from '@xivdyetools/auth';
67
+
68
+ // Constant-time string comparison (prevents timing attacks)
69
+ const isEqual = await timingSafeEqual(userInput, expectedValue);
70
+ ```
71
+
72
+ ### Discord Signature Verification
73
+
74
+ ```typescript
75
+ import { verifyDiscordRequest } from '@xivdyetools/auth';
76
+
77
+ // Verify Discord interaction signature
78
+ const result = await verifyDiscordRequest(request, env.DISCORD_PUBLIC_KEY);
79
+
80
+ if (!result.valid) {
81
+ return new Response('Unauthorized', { status: 401 });
82
+ }
83
+
84
+ // result.body contains the parsed interaction
85
+ const interaction = result.body;
86
+ ```
87
+
88
+ ## Subpath Exports
89
+
90
+ Import only what you need for optimal tree-shaking:
91
+
92
+ ```typescript
93
+ // JWT utilities only
94
+ import { verifyJWT, decodeJWT } from '@xivdyetools/auth/jwt';
95
+
96
+ // HMAC utilities only
97
+ import { hmacSign, hmacVerify } from '@xivdyetools/auth/hmac';
98
+
99
+ // Timing utilities only
100
+ import { timingSafeEqual } from '@xivdyetools/auth/timing';
101
+
102
+ // Discord utilities only
103
+ import { verifyDiscordRequest } from '@xivdyetools/auth/discord';
104
+ ```
105
+
106
+ ## API Reference
107
+
108
+ ### JWT (`@xivdyetools/auth/jwt`)
109
+
110
+ | Function | Description |
111
+ |----------|-------------|
112
+ | `verifyJWT(token, secret)` | Verify JWT signature, algorithm (HS256 only), and expiration |
113
+ | `verifyJWTSignatureOnly(token, secret, maxAgeMs?)` | Verify signature only (for refresh token grace periods) |
114
+ | `decodeJWT(token)` | Decode JWT without verification (debugging only) |
115
+ | `isJWTExpired(payload)` | Check if JWT payload is expired |
116
+ | `getJWTTimeToExpiry(payload)` | Get milliseconds until JWT expires |
117
+
118
+ ### HMAC (`@xivdyetools/auth/hmac`)
119
+
120
+ | Function | Description |
121
+ |----------|-------------|
122
+ | `createHmacKey(secret, usage)` | Create CryptoKey for HMAC operations |
123
+ | `hmacSign(data, secret)` | Sign data, return base64url signature |
124
+ | `hmacSignHex(data, secret)` | Sign data, return hex signature |
125
+ | `hmacVerify(data, signature, secret)` | Verify base64url signature |
126
+ | `hmacVerifyHex(data, signature, secret)` | Verify hex signature |
127
+ | `verifyBotSignature(sig, ts, userId, userName, secret, opts?)` | Verify bot request signature |
128
+
129
+ ### Timing (`@xivdyetools/auth/timing`)
130
+
131
+ | Function | Description |
132
+ |----------|-------------|
133
+ | `timingSafeEqual(a, b)` | Constant-time string comparison |
134
+ | `timingSafeEqualBytes(a, b)` | Constant-time Uint8Array comparison |
135
+
136
+ ### Discord (`@xivdyetools/auth/discord`)
137
+
138
+ | Function | Description |
139
+ |----------|-------------|
140
+ | `verifyDiscordRequest(request, publicKey, opts?)` | Verify Discord Ed25519 signature |
141
+ | `unauthorizedResponse()` | Return 401 response |
142
+ | `badRequestResponse(message?)` | Return 400 response |
143
+
144
+ ## Security Features
145
+
146
+ - **Algorithm Validation**: JWT verification only accepts HS256, preventing algorithm confusion attacks
147
+ - **Timing-Safe Comparison**: Uses `crypto.subtle.timingSafeEqual()` with XOR fallback
148
+ - **Timestamp Validation**: Bot signatures include clock skew tolerance and max age checks
149
+ - **Body Size Limits**: Discord verification enforces 100KB max body size by default
150
+
151
+ ## Dependencies
152
+
153
+ - `@xivdyetools/crypto` - Base64URL and hex encoding utilities
154
+ - `discord-interactions` - Discord Ed25519 signature verification
155
+
156
+ ## License
157
+
158
+ MIT
@@ -1 +1 @@
1
- {"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAe,GACzC,OAAO,CAAC,SAAS,CAAC,CAcpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAQlB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAgClB"}
1
+ {"version":3,"file":"hmac.d.ts","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AASH;;GAEG;AACH,MAAM,WAAW,mBAAmB;IAClC,oEAAoE;IACpE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,aAAa,CACjC,MAAM,EAAE,MAAM,EACd,KAAK,GAAE,MAAM,GAAG,QAAQ,GAAG,MAAe,GACzC,OAAO,CAAC,SAAS,CAAC,CAcpB;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,QAAQ,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,CAK5E;AAED;;;;;;;;;;;GAWG;AACH,wBAAsB,WAAW,CAC/B,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,MAAM,CAAC,CAKjB;AAED;;;;;;;GAOG;AACH,wBAAsB,UAAU,CAC9B,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAgBlB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,IAAI,EAAE,MAAM,EACZ,SAAS,EAAE,MAAM,EACjB,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,OAAO,CAAC,CAelB;AAED;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAsB,kBAAkB,CACtC,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,SAAS,EAAE,MAAM,GAAG,SAAS,EAC7B,aAAa,EAAE,MAAM,GAAG,SAAS,EACjC,QAAQ,EAAE,MAAM,GAAG,SAAS,EAC5B,MAAM,EAAE,MAAM,EACd,OAAO,GAAE,mBAAwB,GAChC,OAAO,CAAC,OAAO,CAAC,CAgClB"}
package/dist/hmac.js CHANGED
@@ -6,7 +6,7 @@
6
6
  *
7
7
  * @module hmac
8
8
  */
9
- import { base64UrlEncodeBytes, bytesToHex, hexToBytes, } from '@xivdyetools/crypto';
9
+ import { base64UrlEncodeBytes, base64UrlDecodeBytes, bytesToHex, hexToBytes, } from '@xivdyetools/crypto';
10
10
  /**
11
11
  * Create an HMAC-SHA256 CryptoKey from a secret string.
12
12
  *
@@ -71,9 +71,11 @@ export async function hmacSignHex(data, secret) {
71
71
  */
72
72
  export async function hmacVerify(data, signature, secret) {
73
73
  try {
74
- const expectedSignature = await hmacSign(data, secret);
75
- // Use timing-safe comparison
76
- return expectedSignature === signature;
74
+ const key = await createHmacKey(secret, 'verify');
75
+ const encoder = new TextEncoder();
76
+ const signatureBytes = base64UrlDecodeBytes(signature);
77
+ // Use crypto.subtle.verify() which is inherently timing-safe
78
+ return crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(data));
77
79
  }
78
80
  catch {
79
81
  return false;
package/dist/hmac.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"hmac.js","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"hmac.js","sourceRoot":"","sources":["../src/hmac.ts"],"names":[],"mappings":"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"}
package/dist/jwt.d.ts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AASH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAY1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAmD5B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqD5B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD"}
1
+ {"version":3,"file":"jwt.d.ts","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAQH;;;;;GAKG;AACH,MAAM,WAAW,UAAU;IACzB,gCAAgC;IAChC,GAAG,EAAE,MAAM,CAAC;IACZ,oCAAoC;IACpC,GAAG,EAAE,MAAM,CAAC;IACZ,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,IAAI,EAAE,QAAQ,GAAG,SAAS,CAAC;IAC3B,uBAAuB;IACvB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,0BAA0B;IAC1B,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CACxB;AAUD;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAY1D;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAkD5B;AAED;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAsB,sBAAsB,CAC1C,KAAK,EAAE,MAAM,EACb,MAAM,EAAE,MAAM,EACd,QAAQ,CAAC,EAAE,MAAM,GAChB,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAqD5B;AAED;;;;;;;GAOG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAOnD;AAED;;;;;GAKG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAOxD"}
package/dist/jwt.js CHANGED
@@ -11,7 +11,7 @@
11
11
  *
12
12
  * @module jwt
13
13
  */
14
- import { base64UrlEncodeBytes, base64UrlDecode, base64UrlDecodeBytes, } from '@xivdyetools/crypto';
14
+ import { base64UrlDecode, base64UrlDecodeBytes, } from '@xivdyetools/crypto';
15
15
  import { createHmacKey } from './hmac.js';
16
16
  /**
17
17
  * Decode a JWT without verifying the signature.
@@ -76,14 +76,14 @@ export async function verifyJWT(token, secret) {
76
76
  if (header.alg !== 'HS256') {
77
77
  return null;
78
78
  }
79
- // Verify signature
79
+ // SECURITY: Verify signature using crypto.subtle.verify() which is
80
+ // inherently timing-safe (comparison happens in native crypto, not JS)
80
81
  const signatureInput = `${headerB64}.${payloadB64}`;
81
- const key = await createHmacKey(secret, 'both');
82
+ const key = await createHmacKey(secret, 'verify');
82
83
  const encoder = new TextEncoder();
83
- const expectedSignature = await crypto.subtle.sign('HMAC', key, encoder.encode(signatureInput));
84
- const expectedSignatureB64 = base64UrlEncodeBytes(new Uint8Array(expectedSignature));
85
- // Compare signatures (using string comparison - both are base64url)
86
- if (signatureB64 !== expectedSignatureB64) {
84
+ const signatureBytes = base64UrlDecodeBytes(signatureB64);
85
+ const isValid = await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(signatureInput));
86
+ if (!isValid) {
87
87
  return null;
88
88
  }
89
89
  // Decode payload
@@ -135,13 +135,14 @@ export async function verifyJWTSignatureOnly(token, secret, maxAgeMs) {
135
135
  if (header.alg !== 'HS256') {
136
136
  return null;
137
137
  }
138
- // Verify signature
138
+ // SECURITY: Verify signature using crypto.subtle.verify() which is
139
+ // inherently timing-safe (comparison happens in native crypto, not JS)
139
140
  const signatureInput = `${headerB64}.${payloadB64}`;
140
- const key = await createHmacKey(secret, 'both');
141
+ const key = await createHmacKey(secret, 'verify');
141
142
  const encoder = new TextEncoder();
142
- const expectedSignature = await crypto.subtle.sign('HMAC', key, encoder.encode(signatureInput));
143
- const expectedSignatureB64 = base64UrlEncodeBytes(new Uint8Array(expectedSignature));
144
- if (signatureB64 !== expectedSignatureB64) {
143
+ const signatureBytes = base64UrlDecodeBytes(signatureB64);
144
+ const isValid = await crypto.subtle.verify('HMAC', key, signatureBytes, encoder.encode(signatureInput));
145
+ if (!isValid) {
145
146
  return null;
146
147
  }
147
148
  // Decode payload
package/dist/jwt.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"jwt.js","sourceRoot":"","sources":["../src/jwt.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xivdyetools/auth",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "Shared authentication utilities for xivdyetools ecosystem",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -45,10 +45,10 @@
45
45
  },
46
46
  "devDependencies": {
47
47
  "@cloudflare/workers-types": "^4.20241224.0",
48
- "@vitest/coverage-v8": "^2.1.8",
48
+ "@vitest/coverage-v8": "^4.0.18",
49
49
  "rimraf": "^6.0.1",
50
50
  "typescript": "^5.7.2",
51
- "vitest": "^2.1.8"
51
+ "vitest": "^4.0.18"
52
52
  },
53
53
  "peerDependencies": {
54
54
  "@cloudflare/workers-types": "^4.0.0"
package/src/hmac.ts CHANGED
@@ -9,6 +9,7 @@
9
9
 
10
10
  import {
11
11
  base64UrlEncodeBytes,
12
+ base64UrlDecodeBytes,
12
13
  bytesToHex,
13
14
  hexToBytes,
14
15
  } from '@xivdyetools/crypto';
@@ -109,9 +110,17 @@ export async function hmacVerify(
109
110
  secret: string
110
111
  ): Promise<boolean> {
111
112
  try {
112
- const expectedSignature = await hmacSign(data, secret);
113
- // Use timing-safe comparison
114
- return expectedSignature === signature;
113
+ const key = await createHmacKey(secret, 'verify');
114
+ const encoder = new TextEncoder();
115
+ const signatureBytes = base64UrlDecodeBytes(signature);
116
+
117
+ // Use crypto.subtle.verify() which is inherently timing-safe
118
+ return crypto.subtle.verify(
119
+ 'HMAC',
120
+ key,
121
+ signatureBytes,
122
+ encoder.encode(data)
123
+ );
115
124
  } catch {
116
125
  return false;
117
126
  }
package/src/jwt.ts CHANGED
@@ -13,7 +13,6 @@
13
13
  */
14
14
 
15
15
  import {
16
- base64UrlEncodeBytes,
17
16
  base64UrlDecode,
18
17
  base64UrlDecodeBytes,
19
18
  } from '@xivdyetools/crypto';
@@ -119,22 +118,21 @@ export async function verifyJWT(
119
118
  return null;
120
119
  }
121
120
 
122
- // Verify signature
121
+ // SECURITY: Verify signature using crypto.subtle.verify() which is
122
+ // inherently timing-safe (comparison happens in native crypto, not JS)
123
123
  const signatureInput = `${headerB64}.${payloadB64}`;
124
- const key = await createHmacKey(secret, 'both');
124
+ const key = await createHmacKey(secret, 'verify');
125
125
  const encoder = new TextEncoder();
126
+ const signatureBytes = base64UrlDecodeBytes(signatureB64);
126
127
 
127
- const expectedSignature = await crypto.subtle.sign(
128
+ const isValid = await crypto.subtle.verify(
128
129
  'HMAC',
129
130
  key,
131
+ signatureBytes,
130
132
  encoder.encode(signatureInput)
131
133
  );
132
- const expectedSignatureB64 = base64UrlEncodeBytes(
133
- new Uint8Array(expectedSignature)
134
- );
135
134
 
136
- // Compare signatures (using string comparison - both are base64url)
137
- if (signatureB64 !== expectedSignatureB64) {
135
+ if (!isValid) {
138
136
  return null;
139
137
  }
140
138
 
@@ -197,21 +195,21 @@ export async function verifyJWTSignatureOnly(
197
195
  return null;
198
196
  }
199
197
 
200
- // Verify signature
198
+ // SECURITY: Verify signature using crypto.subtle.verify() which is
199
+ // inherently timing-safe (comparison happens in native crypto, not JS)
201
200
  const signatureInput = `${headerB64}.${payloadB64}`;
202
- const key = await createHmacKey(secret, 'both');
201
+ const key = await createHmacKey(secret, 'verify');
203
202
  const encoder = new TextEncoder();
203
+ const signatureBytes = base64UrlDecodeBytes(signatureB64);
204
204
 
205
- const expectedSignature = await crypto.subtle.sign(
205
+ const isValid = await crypto.subtle.verify(
206
206
  'HMAC',
207
207
  key,
208
+ signatureBytes,
208
209
  encoder.encode(signatureInput)
209
210
  );
210
- const expectedSignatureB64 = base64UrlEncodeBytes(
211
- new Uint8Array(expectedSignature)
212
- );
213
211
 
214
- if (signatureB64 !== expectedSignatureB64) {
212
+ if (!isValid) {
215
213
  return null;
216
214
  }
217
215