@vurb/jwt 3.1.31
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 +79 -0
- package/dist/JwtVerifier.d.ts +128 -0
- package/dist/JwtVerifier.d.ts.map +1 -0
- package/dist/JwtVerifier.js +259 -0
- package/dist/JwtVerifier.js.map +1 -0
- package/dist/createJwtAuthTool.d.ts +43 -0
- package/dist/createJwtAuthTool.d.ts.map +1 -0
- package/dist/createJwtAuthTool.js +115 -0
- package/dist/createJwtAuthTool.js.map +1 -0
- package/dist/index.d.ts +31 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +28 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware.d.ts +54 -0
- package/dist/middleware.d.ts.map +1 -0
- package/dist/middleware.js +91 -0
- package/dist/middleware.js.map +1 -0
- package/package.json +62 -0
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<h1 align="center">@vurb/jwt</h1>
|
|
3
|
+
<p align="center">
|
|
4
|
+
<strong>JWT Verification Middleware</strong> — Standards-compliant token validation for Vurb.ts servers
|
|
5
|
+
</p>
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
<p align="center">
|
|
9
|
+
<a href="https://www.npmjs.com/package/@vurb/jwt"><img src="https://img.shields.io/npm/v/@vurb/jwt?color=blue" alt="npm" /></a>
|
|
10
|
+
<a href="https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-green" alt="License" /></a>
|
|
11
|
+
<img src="https://img.shields.io/badge/node-%3E%3D18-brightgreen" alt="Node" />
|
|
12
|
+
</p>
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
> JWT verification middleware for MCP servers built with Vurb.ts. Timing-safe validation with `jose`, JWKS auto-discovery, and self-healing error responses.
|
|
17
|
+
|
|
18
|
+
## Quick Start
|
|
19
|
+
|
|
20
|
+
```typescript
|
|
21
|
+
import { initVurb } from '@vurb/core';
|
|
22
|
+
import { jwtGuard } from '@vurb/jwt';
|
|
23
|
+
|
|
24
|
+
const f = initVurb<AppContext>();
|
|
25
|
+
|
|
26
|
+
const withJwt = jwtGuard({
|
|
27
|
+
secret: process.env.JWT_SECRET!,
|
|
28
|
+
algorithms: ['HS256'],
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
export default f.query('billing.invoices')
|
|
32
|
+
.use(withJwt)
|
|
33
|
+
.handle(async (input, ctx) => {
|
|
34
|
+
// ctx.jwt contains the decoded payload
|
|
35
|
+
return db.invoices.findMany({ where: { tenantId: ctx.jwt.sub } });
|
|
36
|
+
});
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Features
|
|
40
|
+
|
|
41
|
+
| Feature | Description |
|
|
42
|
+
|---------|-------------|
|
|
43
|
+
| **Algorithms** | HS256, RS256, ES256 — all standard algorithms via `jose` |
|
|
44
|
+
| **JWKS** | Auto-discovery from `/.well-known/jwks.json` with key rotation |
|
|
45
|
+
| **Self-Healing** | Expired/invalid tokens return actionable hints to the LLM agent |
|
|
46
|
+
| **Timing-Safe** | Constant-time signature verification |
|
|
47
|
+
| **Zero Config** | Works with Auth0, Clerk, Supabase, Firebase, any OIDC provider |
|
|
48
|
+
|
|
49
|
+
## JWKS Auto-Discovery
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const withJwt = jwtGuard({
|
|
53
|
+
jwksUri: 'https://auth.example.com/.well-known/jwks.json',
|
|
54
|
+
issuer: 'https://auth.example.com/',
|
|
55
|
+
audience: 'my-mcp-server',
|
|
56
|
+
});
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Installation
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
npm install @vurb/jwt jose
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
### Peer Dependencies
|
|
66
|
+
|
|
67
|
+
| Package | Version |
|
|
68
|
+
|---------|---------|
|
|
69
|
+
| `vurb` | `^2.0.0` |
|
|
70
|
+
| `jose` | `^5.0.0` (optional) |
|
|
71
|
+
|
|
72
|
+
## Requirements
|
|
73
|
+
|
|
74
|
+
- **Node.js** ≥ 18.0.0
|
|
75
|
+
- **Vurb.ts** ≥ 2.0.0 (peer dependency)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
[Apache-2.0](https://github.com/vinkius-labs/vurb.ts/blob/main/LICENSE)
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Verifier — Standards-Compliant Token Verification
|
|
3
|
+
*
|
|
4
|
+
* Verifies JWTs using `jose` when installed, or falls back to
|
|
5
|
+
* native `crypto.subtle` (Web Crypto API) for HS256 verification.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - HS256, RS256, ES256 (via jose)
|
|
9
|
+
* - HS256 native fallback (via crypto.subtle)
|
|
10
|
+
* - JWKS endpoint auto-discovery
|
|
11
|
+
* - Claims validation: `exp`, `nbf`, `iss`, `aud`
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { JwtVerifier } from '@vurb/jwt';
|
|
16
|
+
*
|
|
17
|
+
* // With symmetric secret (HS256)
|
|
18
|
+
* const verifier = new JwtVerifier({ secret: 'my-secret' });
|
|
19
|
+
*
|
|
20
|
+
* // With JWKS endpoint (RS256, ES256)
|
|
21
|
+
* const verifier = new JwtVerifier({ jwksUri: 'https://auth.example.com/.well-known/jwks.json' });
|
|
22
|
+
*
|
|
23
|
+
* const payload = await verifier.verify(token);
|
|
24
|
+
* if (payload) console.log(payload.sub); // user ID
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export interface JwtVerifierConfig {
|
|
28
|
+
/** Symmetric secret for HS256. */
|
|
29
|
+
readonly secret?: string;
|
|
30
|
+
/** JWKS endpoint URL for RS256/ES256 key fetching. Requires `jose`. */
|
|
31
|
+
readonly jwksUri?: string;
|
|
32
|
+
/** PEM-encoded public key for RS256/ES256. */
|
|
33
|
+
readonly publicKey?: string;
|
|
34
|
+
/** Algorithm for public key verification (e.g. 'RS256', 'ES256', 'ES384', 'ES512'). Default: 'RS256'. */
|
|
35
|
+
readonly algorithm?: string;
|
|
36
|
+
/** Expected issuer (`iss` claim). */
|
|
37
|
+
readonly issuer?: string | string[];
|
|
38
|
+
/** Expected audience (`aud` claim). */
|
|
39
|
+
readonly audience?: string | string[];
|
|
40
|
+
/** Clock tolerance in seconds for `exp`/`nbf` checks. Default: 60 */
|
|
41
|
+
readonly clockTolerance?: number;
|
|
42
|
+
/** Required claims that must be present in the payload. */
|
|
43
|
+
readonly requiredClaims?: string[];
|
|
44
|
+
}
|
|
45
|
+
export interface JwtPayload {
|
|
46
|
+
/** Subject — typically the user ID */
|
|
47
|
+
readonly sub?: string;
|
|
48
|
+
/** Issuer */
|
|
49
|
+
readonly iss?: string;
|
|
50
|
+
/** Audience */
|
|
51
|
+
readonly aud?: string | string[];
|
|
52
|
+
/** Expiration time (Unix seconds) */
|
|
53
|
+
readonly exp?: number;
|
|
54
|
+
/** Not before (Unix seconds) */
|
|
55
|
+
readonly nbf?: number;
|
|
56
|
+
/** Issued at (Unix seconds) */
|
|
57
|
+
readonly iat?: number;
|
|
58
|
+
/** JWT ID */
|
|
59
|
+
readonly jti?: string;
|
|
60
|
+
/** Additional claims */
|
|
61
|
+
readonly [key: string]: unknown;
|
|
62
|
+
}
|
|
63
|
+
export interface JwtVerifyResult {
|
|
64
|
+
/** Whether the token is valid */
|
|
65
|
+
readonly valid: boolean;
|
|
66
|
+
/** Decoded payload (only present if valid) */
|
|
67
|
+
readonly payload?: JwtPayload;
|
|
68
|
+
/** Error reason (only present if invalid) */
|
|
69
|
+
readonly reason?: string;
|
|
70
|
+
}
|
|
71
|
+
export declare class JwtVerifier {
|
|
72
|
+
private readonly _config;
|
|
73
|
+
private readonly _clockTolerance;
|
|
74
|
+
private _joseLoaded;
|
|
75
|
+
private _jwtVerify;
|
|
76
|
+
private _createRemoteJWKSet;
|
|
77
|
+
private _jwks;
|
|
78
|
+
constructor(config: JwtVerifierConfig);
|
|
79
|
+
/**
|
|
80
|
+
* Verify a JWT and return the decoded payload.
|
|
81
|
+
*
|
|
82
|
+
* @param token - Raw JWT string (without "Bearer " prefix)
|
|
83
|
+
* @returns The decoded payload, or `null` if verification fails
|
|
84
|
+
*/
|
|
85
|
+
verify(token: string): Promise<JwtPayload | null>;
|
|
86
|
+
/**
|
|
87
|
+
* Verify a JWT with detailed result including error reason.
|
|
88
|
+
*
|
|
89
|
+
* @param token - Raw JWT string
|
|
90
|
+
* @returns Detailed verification result
|
|
91
|
+
*/
|
|
92
|
+
verifyDetailed(token: string): Promise<JwtVerifyResult>;
|
|
93
|
+
/**
|
|
94
|
+
* Attempt to verify using jose.
|
|
95
|
+
* Returns `undefined` if jose is not installed.
|
|
96
|
+
*
|
|
97
|
+
* @internal
|
|
98
|
+
*/
|
|
99
|
+
private _verifyWithJose;
|
|
100
|
+
/**
|
|
101
|
+
* Load jose dynamically. Silently fails if not installed.
|
|
102
|
+
* @internal
|
|
103
|
+
*/
|
|
104
|
+
private _loadJose;
|
|
105
|
+
/**
|
|
106
|
+
* Verify HS256 JWT using Node.js native crypto.
|
|
107
|
+
* @internal
|
|
108
|
+
*/
|
|
109
|
+
private _verifyHS256Native;
|
|
110
|
+
/**
|
|
111
|
+
* Validate claims (exp, nbf, required) on an already signature-verified payload.
|
|
112
|
+
* @internal
|
|
113
|
+
*/
|
|
114
|
+
private _validateClaims;
|
|
115
|
+
/**
|
|
116
|
+
* Decode a JWT payload WITHOUT verifying the signature.
|
|
117
|
+
* Useful for inspecting tokens in logging/debugging.
|
|
118
|
+
*
|
|
119
|
+
* ⚠️ Never trust decoded-only payloads for authorization decisions.
|
|
120
|
+
*/
|
|
121
|
+
static decode(token: string): JwtPayload | null;
|
|
122
|
+
/**
|
|
123
|
+
* Check if a token is expired without verifying signature.
|
|
124
|
+
* Returns `true` if expired or unparseable.
|
|
125
|
+
*/
|
|
126
|
+
static isExpired(token: string, clockTolerance?: number): boolean;
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=JwtVerifier.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JwtVerifier.d.ts","sourceRoot":"","sources":["../src/JwtVerifier.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AAQH,MAAM,WAAW,iBAAiB;IAC9B,kCAAkC;IAClC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;IAEzB,uEAAuE;IACvE,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAE1B,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,yGAAyG;IACzG,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,qCAAqC;IACrC,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEpC,uCAAuC;IACvC,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAEtC,qEAAqE;IACrE,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAEjC,2DAA2D;IAC3D,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,EAAE,CAAC;CACtC;AAED,MAAM,WAAW,UAAU;IACvB,sCAAsC;IACtC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa;IACb,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,eAAe;IACf,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACjC,qCAAqC;IACrC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,gCAAgC;IAChC,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,+BAA+B;IAC/B,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa;IACb,QAAQ,CAAC,GAAG,CAAC,EAAE,MAAM,CAAC;IACtB,wBAAwB;IACxB,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACnC;AAED,MAAM,WAAW,eAAe;IAC5B,iCAAiC;IACjC,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,CAAC,EAAE,UAAU,CAAC;IAC9B,6CAA6C;IAC7C,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAC;CAC5B;AAMD,qBAAa,WAAW;IACpB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAoB;IAC5C,OAAO,CAAC,QAAQ,CAAC,eAAe,CAAS;IAGzC,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,UAAU,CAAuG;IACzH,OAAO,CAAC,mBAAmB,CAAwC;IACnE,OAAO,CAAC,KAAK,CAAiB;gBAElB,MAAM,EAAE,iBAAiB;IAUrC;;;;;OAKG;IACG,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC;IAKvD;;;;;OAKG;IACG,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IA8B7D;;;;;OAKG;YACW,eAAe;IAkC7B;;;OAGG;YACW,SAAS;IAavB;;;OAGG;IACH,OAAO,CAAC,kBAAkB;IA0C1B;;;OAGG;IACH,OAAO,CAAC,eAAe;IAgDvB;;;;;OAKG;IACH,MAAM,CAAC,MAAM,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI;IAU/C;;;OAGG;IACH,MAAM,CAAC,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,cAAc,SAAK,GAAG,OAAO;CAKhE"}
|
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Verifier — Standards-Compliant Token Verification
|
|
3
|
+
*
|
|
4
|
+
* Verifies JWTs using `jose` when installed, or falls back to
|
|
5
|
+
* native `crypto.subtle` (Web Crypto API) for HS256 verification.
|
|
6
|
+
*
|
|
7
|
+
* Supports:
|
|
8
|
+
* - HS256, RS256, ES256 (via jose)
|
|
9
|
+
* - HS256 native fallback (via crypto.subtle)
|
|
10
|
+
* - JWKS endpoint auto-discovery
|
|
11
|
+
* - Claims validation: `exp`, `nbf`, `iss`, `aud`
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```ts
|
|
15
|
+
* import { JwtVerifier } from '@vurb/jwt';
|
|
16
|
+
*
|
|
17
|
+
* // With symmetric secret (HS256)
|
|
18
|
+
* const verifier = new JwtVerifier({ secret: 'my-secret' });
|
|
19
|
+
*
|
|
20
|
+
* // With JWKS endpoint (RS256, ES256)
|
|
21
|
+
* const verifier = new JwtVerifier({ jwksUri: 'https://auth.example.com/.well-known/jwks.json' });
|
|
22
|
+
*
|
|
23
|
+
* const payload = await verifier.verify(token);
|
|
24
|
+
* if (payload) console.log(payload.sub); // user ID
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
import * as crypto from 'node:crypto';
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// JwtVerifier
|
|
30
|
+
// ============================================================================
|
|
31
|
+
export class JwtVerifier {
|
|
32
|
+
_config;
|
|
33
|
+
_clockTolerance;
|
|
34
|
+
// Lazy-loaded jose references
|
|
35
|
+
_joseLoaded = false;
|
|
36
|
+
_jwtVerify = null;
|
|
37
|
+
_createRemoteJWKSet = null;
|
|
38
|
+
_jwks = null;
|
|
39
|
+
constructor(config) {
|
|
40
|
+
if (!config.secret && !config.jwksUri && !config.publicKey) {
|
|
41
|
+
throw new Error('JwtVerifier requires at least one of: secret, jwksUri, publicKey');
|
|
42
|
+
}
|
|
43
|
+
this._config = config;
|
|
44
|
+
this._clockTolerance = config.clockTolerance ?? 60;
|
|
45
|
+
}
|
|
46
|
+
// ── Public API ───────────────────────────────────────
|
|
47
|
+
/**
|
|
48
|
+
* Verify a JWT and return the decoded payload.
|
|
49
|
+
*
|
|
50
|
+
* @param token - Raw JWT string (without "Bearer " prefix)
|
|
51
|
+
* @returns The decoded payload, or `null` if verification fails
|
|
52
|
+
*/
|
|
53
|
+
async verify(token) {
|
|
54
|
+
const result = await this.verifyDetailed(token);
|
|
55
|
+
return result.valid ? result.payload : null;
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* Verify a JWT with detailed result including error reason.
|
|
59
|
+
*
|
|
60
|
+
* @param token - Raw JWT string
|
|
61
|
+
* @returns Detailed verification result
|
|
62
|
+
*/
|
|
63
|
+
async verifyDetailed(token) {
|
|
64
|
+
if (!token || typeof token !== 'string') {
|
|
65
|
+
return { valid: false, reason: 'Token is empty or not a string' };
|
|
66
|
+
}
|
|
67
|
+
try {
|
|
68
|
+
// Try jose first (supports all algorithms)
|
|
69
|
+
const payload = await this._verifyWithJose(token);
|
|
70
|
+
if (payload !== undefined) {
|
|
71
|
+
return this._validateClaims(payload);
|
|
72
|
+
}
|
|
73
|
+
// Fallback: native HS256 verification (secret only)
|
|
74
|
+
if (this._config.secret) {
|
|
75
|
+
const payload = this._verifyHS256Native(token);
|
|
76
|
+
if (payload) {
|
|
77
|
+
return this._validateClaims(payload);
|
|
78
|
+
}
|
|
79
|
+
return { valid: false, reason: 'Signature verification failed' };
|
|
80
|
+
}
|
|
81
|
+
return { valid: false, reason: 'No verification method available (install jose for RS256/ES256)' };
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
85
|
+
return { valid: false, reason: message };
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
// ── Jose Integration ─────────────────────────────────
|
|
89
|
+
/**
|
|
90
|
+
* Attempt to verify using jose.
|
|
91
|
+
* Returns `undefined` if jose is not installed.
|
|
92
|
+
*
|
|
93
|
+
* @internal
|
|
94
|
+
*/
|
|
95
|
+
async _verifyWithJose(token) {
|
|
96
|
+
if (!this._joseLoaded) {
|
|
97
|
+
await this._loadJose();
|
|
98
|
+
}
|
|
99
|
+
if (!this._jwtVerify)
|
|
100
|
+
return undefined;
|
|
101
|
+
const options = {};
|
|
102
|
+
if (this._config.issuer)
|
|
103
|
+
options['issuer'] = this._config.issuer;
|
|
104
|
+
if (this._config.audience)
|
|
105
|
+
options['audience'] = this._config.audience;
|
|
106
|
+
options['clockTolerance'] = this._clockTolerance;
|
|
107
|
+
let key;
|
|
108
|
+
if (this._config.jwksUri) {
|
|
109
|
+
if (!this._jwks && this._createRemoteJWKSet) {
|
|
110
|
+
this._jwks = this._createRemoteJWKSet(new URL(this._config.jwksUri));
|
|
111
|
+
}
|
|
112
|
+
key = this._jwks;
|
|
113
|
+
}
|
|
114
|
+
else if (this._config.publicKey) {
|
|
115
|
+
// jose importSPKI for PEM keys
|
|
116
|
+
const jose = await import('jose');
|
|
117
|
+
key = await jose.importSPKI(this._config.publicKey, this._config.algorithm ?? 'RS256');
|
|
118
|
+
}
|
|
119
|
+
else if (this._config.secret) {
|
|
120
|
+
const encoder = new TextEncoder();
|
|
121
|
+
key = encoder.encode(this._config.secret);
|
|
122
|
+
}
|
|
123
|
+
if (!key)
|
|
124
|
+
return undefined;
|
|
125
|
+
const result = await this._jwtVerify(token, key, options);
|
|
126
|
+
return result.payload;
|
|
127
|
+
}
|
|
128
|
+
/**
|
|
129
|
+
* Load jose dynamically. Silently fails if not installed.
|
|
130
|
+
* @internal
|
|
131
|
+
*/
|
|
132
|
+
async _loadJose() {
|
|
133
|
+
this._joseLoaded = true;
|
|
134
|
+
try {
|
|
135
|
+
const jose = await import('jose');
|
|
136
|
+
this._jwtVerify = jose.jwtVerify;
|
|
137
|
+
this._createRemoteJWKSet = jose.createRemoteJWKSet;
|
|
138
|
+
}
|
|
139
|
+
catch {
|
|
140
|
+
// jose not installed — will use native fallback
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
// ── Native HS256 Fallback ────────────────────────────
|
|
144
|
+
/**
|
|
145
|
+
* Verify HS256 JWT using Node.js native crypto.
|
|
146
|
+
* @internal
|
|
147
|
+
*/
|
|
148
|
+
_verifyHS256Native(token) {
|
|
149
|
+
const parts = token.split('.');
|
|
150
|
+
if (parts.length !== 3)
|
|
151
|
+
return null;
|
|
152
|
+
const headerB64 = parts[0];
|
|
153
|
+
const payloadB64 = parts[1];
|
|
154
|
+
const signatureB64 = parts[2];
|
|
155
|
+
// Verify header
|
|
156
|
+
try {
|
|
157
|
+
const header = JSON.parse(Buffer.from(headerB64, 'base64url').toString());
|
|
158
|
+
if (header.alg !== 'HS256')
|
|
159
|
+
return null; // Only HS256 in native mode
|
|
160
|
+
}
|
|
161
|
+
catch {
|
|
162
|
+
return null;
|
|
163
|
+
}
|
|
164
|
+
// Verify signature
|
|
165
|
+
const expectedSignature = crypto
|
|
166
|
+
.createHmac('sha256', this._config.secret)
|
|
167
|
+
.update(`${headerB64}.${payloadB64}`)
|
|
168
|
+
.digest('base64url');
|
|
169
|
+
// Guard against RangeError from timingSafeEqual when
|
|
170
|
+
// a malformed JWT carries a truncated or oversized signature.
|
|
171
|
+
// The length check is safe — the expected length (32 bytes for
|
|
172
|
+
// SHA-256) is publicly known and leaks no secret information.
|
|
173
|
+
const sigBuf = Buffer.from(signatureB64, 'base64url');
|
|
174
|
+
const expBuf = Buffer.from(expectedSignature, 'base64url');
|
|
175
|
+
if (sigBuf.length !== expBuf.length || !crypto.timingSafeEqual(sigBuf, expBuf)) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
// Decode payload
|
|
179
|
+
try {
|
|
180
|
+
return JSON.parse(Buffer.from(payloadB64, 'base64url').toString());
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
return null;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
// ── Claims Validation ────────────────────────────────
|
|
187
|
+
/**
|
|
188
|
+
* Validate claims (exp, nbf, required) on an already signature-verified payload.
|
|
189
|
+
* @internal
|
|
190
|
+
*/
|
|
191
|
+
_validateClaims(payload) {
|
|
192
|
+
const now = Math.floor(Date.now() / 1000);
|
|
193
|
+
// Check expiration
|
|
194
|
+
if (payload.exp !== undefined) {
|
|
195
|
+
if (now > payload.exp + this._clockTolerance) {
|
|
196
|
+
return { valid: false, reason: 'Token has expired' };
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
// Check not-before
|
|
200
|
+
if (payload.nbf !== undefined) {
|
|
201
|
+
if (now < payload.nbf - this._clockTolerance) {
|
|
202
|
+
return { valid: false, reason: 'Token is not yet valid (nbf)' };
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
// Check issuer (when not using jose, which validates it internally)
|
|
206
|
+
if (this._config.issuer && !this._jwtVerify) {
|
|
207
|
+
const issuers = Array.isArray(this._config.issuer) ? this._config.issuer : [this._config.issuer];
|
|
208
|
+
if (!payload.iss || !issuers.includes(payload.iss)) {
|
|
209
|
+
return { valid: false, reason: `Invalid issuer: ${payload.iss}` };
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
// Check audience (when not using jose)
|
|
213
|
+
if (this._config.audience && !this._jwtVerify) {
|
|
214
|
+
const audiences = Array.isArray(this._config.audience) ? this._config.audience : [this._config.audience];
|
|
215
|
+
const tokenAud = Array.isArray(payload.aud) ? payload.aud : payload.aud ? [payload.aud] : [];
|
|
216
|
+
if (!tokenAud.some(a => audiences.includes(a))) {
|
|
217
|
+
return { valid: false, reason: `Invalid audience: ${payload.aud}` };
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// Check required claims
|
|
221
|
+
if (this._config.requiredClaims) {
|
|
222
|
+
for (const claim of this._config.requiredClaims) {
|
|
223
|
+
if (payload[claim] === undefined) {
|
|
224
|
+
return { valid: false, reason: `Missing required claim: ${claim}` };
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
return { valid: true, payload };
|
|
229
|
+
}
|
|
230
|
+
// ── Utilities ────────────────────────────────────────
|
|
231
|
+
/**
|
|
232
|
+
* Decode a JWT payload WITHOUT verifying the signature.
|
|
233
|
+
* Useful for inspecting tokens in logging/debugging.
|
|
234
|
+
*
|
|
235
|
+
* ⚠️ Never trust decoded-only payloads for authorization decisions.
|
|
236
|
+
*/
|
|
237
|
+
static decode(token) {
|
|
238
|
+
try {
|
|
239
|
+
const parts = token.split('.');
|
|
240
|
+
if (parts.length !== 3)
|
|
241
|
+
return null;
|
|
242
|
+
return JSON.parse(Buffer.from(parts[1], 'base64url').toString());
|
|
243
|
+
}
|
|
244
|
+
catch {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/**
|
|
249
|
+
* Check if a token is expired without verifying signature.
|
|
250
|
+
* Returns `true` if expired or unparseable.
|
|
251
|
+
*/
|
|
252
|
+
static isExpired(token, clockTolerance = 60) {
|
|
253
|
+
const payload = JwtVerifier.decode(token);
|
|
254
|
+
if (!payload?.exp)
|
|
255
|
+
return true;
|
|
256
|
+
return Math.floor(Date.now() / 1000) > payload.exp + clockTolerance;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
//# sourceMappingURL=JwtVerifier.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"JwtVerifier.js","sourceRoot":"","sources":["../src/JwtVerifier.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Auth Tool Factory — Pre-built JWT Verification Tool
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete vurb tool with verify and status actions.
|
|
5
|
+
* Consumers only need to provide their JWT verification config.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { createJwtAuthTool } from '@vurb/jwt';
|
|
10
|
+
*
|
|
11
|
+
* const jwtTool = createJwtAuthTool({
|
|
12
|
+
* secret: process.env.JWT_SECRET!,
|
|
13
|
+
* toolName: 'jwt_auth',
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { JwtVerifierConfig } from './JwtVerifier.js';
|
|
18
|
+
export interface JwtAuthToolConfig<TContext = unknown> extends JwtVerifierConfig {
|
|
19
|
+
/** Tool name in MCP. Default: 'jwt_auth' */
|
|
20
|
+
readonly toolName?: string;
|
|
21
|
+
/** Tool description for the LLM. */
|
|
22
|
+
readonly description?: string;
|
|
23
|
+
/** Tags for selective tool exposure. */
|
|
24
|
+
readonly tags?: string[];
|
|
25
|
+
/**
|
|
26
|
+
* Extracts the JWT from the context.
|
|
27
|
+
* Required for the `status` action.
|
|
28
|
+
*/
|
|
29
|
+
readonly extractToken?: (ctx: TContext) => string | null | undefined;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Creates a complete JWT auth tool with verify and status actions.
|
|
33
|
+
*
|
|
34
|
+
* Actions:
|
|
35
|
+
* - `verify` — Verify a JWT and return decoded claims
|
|
36
|
+
* - `status` — Check if JWT is present/valid/expired
|
|
37
|
+
*/
|
|
38
|
+
export declare function createJwtAuthTool<TContext = unknown>(config: JwtAuthToolConfig<TContext>): import("vurb").GroupedToolBuilder<TContext, Record<string, never>, string, Record<string, never> & {
|
|
39
|
+
[x: `${string}.verify`]: Record<string, unknown>;
|
|
40
|
+
} & {
|
|
41
|
+
[x: `${string}.status`]: Record<string, unknown>;
|
|
42
|
+
}>;
|
|
43
|
+
//# sourceMappingURL=createJwtAuthTool.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createJwtAuthTool.d.ts","sourceRoot":"","sources":["../src/createJwtAuthTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAc,MAAM,kBAAkB,CAAC;AAMtE,MAAM,WAAW,iBAAiB,CAAC,QAAQ,GAAG,OAAO,CAAE,SAAQ,iBAAiB;IAC5E,4CAA4C;IAC5C,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAE3B,oCAAoC;IACpC,QAAQ,CAAC,WAAW,CAAC,EAAE,MAAM,CAAC;IAE9B,wCAAwC;IACxC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAEzB;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,QAAQ,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;CACxE;AAkBD;;;;;;GAMG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,GAAG,OAAO,EAAE,MAAM,EAAE,iBAAiB,CAAC,QAAQ,CAAC;;;;GAuFxF"}
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Auth Tool Factory — Pre-built JWT Verification Tool
|
|
3
|
+
*
|
|
4
|
+
* Creates a complete vurb tool with verify and status actions.
|
|
5
|
+
* Consumers only need to provide their JWT verification config.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { createJwtAuthTool } from '@vurb/jwt';
|
|
10
|
+
*
|
|
11
|
+
* const jwtTool = createJwtAuthTool({
|
|
12
|
+
* secret: process.env.JWT_SECRET!,
|
|
13
|
+
* toolName: 'jwt_auth',
|
|
14
|
+
* });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { createTool } from 'vurb';
|
|
18
|
+
import { JwtVerifier } from './JwtVerifier.js';
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Response Helpers
|
|
21
|
+
// ============================================================================
|
|
22
|
+
function ok(data) {
|
|
23
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] };
|
|
24
|
+
}
|
|
25
|
+
function fail(data) {
|
|
26
|
+
return { content: [{ type: 'text', text: JSON.stringify(data, null, 2) }], isError: true };
|
|
27
|
+
}
|
|
28
|
+
// ============================================================================
|
|
29
|
+
// Factory
|
|
30
|
+
// ============================================================================
|
|
31
|
+
/**
|
|
32
|
+
* Creates a complete JWT auth tool with verify and status actions.
|
|
33
|
+
*
|
|
34
|
+
* Actions:
|
|
35
|
+
* - `verify` — Verify a JWT and return decoded claims
|
|
36
|
+
* - `status` — Check if JWT is present/valid/expired
|
|
37
|
+
*/
|
|
38
|
+
export function createJwtAuthTool(config) {
|
|
39
|
+
const verifier = new JwtVerifier(config);
|
|
40
|
+
const toolName = config.toolName ?? 'jwt_auth';
|
|
41
|
+
const description = config.description ?? 'JWT authentication — verify tokens and check status';
|
|
42
|
+
const extractToken = config.extractToken;
|
|
43
|
+
const tool = createTool(toolName);
|
|
44
|
+
if (config.tags?.length) {
|
|
45
|
+
tool.tags(...config.tags);
|
|
46
|
+
}
|
|
47
|
+
return tool
|
|
48
|
+
.action({
|
|
49
|
+
name: 'verify',
|
|
50
|
+
description: 'Verify a JWT and return decoded claims',
|
|
51
|
+
handler: async (_ctx, args) => {
|
|
52
|
+
const token = args['token'];
|
|
53
|
+
if (!token) {
|
|
54
|
+
return fail({ message: 'Token is required' });
|
|
55
|
+
}
|
|
56
|
+
const rawToken = token.startsWith('Bearer ') ? token.slice(7) : token;
|
|
57
|
+
const result = await verifier.verifyDetailed(rawToken);
|
|
58
|
+
if (!result.valid) {
|
|
59
|
+
return fail({
|
|
60
|
+
message: `Verification failed: ${result.reason}`,
|
|
61
|
+
valid: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
return ok({
|
|
65
|
+
valid: true,
|
|
66
|
+
payload: result.payload,
|
|
67
|
+
claims: {
|
|
68
|
+
sub: result.payload?.sub,
|
|
69
|
+
iss: result.payload?.iss,
|
|
70
|
+
aud: result.payload?.aud,
|
|
71
|
+
exp: result.payload?.exp,
|
|
72
|
+
iat: result.payload?.iat,
|
|
73
|
+
},
|
|
74
|
+
});
|
|
75
|
+
},
|
|
76
|
+
})
|
|
77
|
+
.action({
|
|
78
|
+
name: 'status',
|
|
79
|
+
description: 'Check JWT authentication status from context',
|
|
80
|
+
handler: async (ctx) => {
|
|
81
|
+
if (!extractToken) {
|
|
82
|
+
return ok({
|
|
83
|
+
available: false,
|
|
84
|
+
reason: 'No token extractor configured',
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
const raw = extractToken(ctx);
|
|
88
|
+
if (!raw) {
|
|
89
|
+
return ok({
|
|
90
|
+
authenticated: false,
|
|
91
|
+
reason: 'No JWT found in context',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
const token = typeof raw === 'string' && raw.startsWith('Bearer ') ? raw.slice(7) : raw;
|
|
95
|
+
// Decode without verification for status info
|
|
96
|
+
const decoded = JwtVerifier.decode(token);
|
|
97
|
+
const isExpired = JwtVerifier.isExpired(token, config.clockTolerance);
|
|
98
|
+
// Full verification
|
|
99
|
+
const result = await verifier.verifyDetailed(token);
|
|
100
|
+
return ok({
|
|
101
|
+
authenticated: result.valid,
|
|
102
|
+
expired: isExpired,
|
|
103
|
+
valid: result.valid,
|
|
104
|
+
reason: result.reason,
|
|
105
|
+
claims: decoded ? {
|
|
106
|
+
sub: decoded.sub,
|
|
107
|
+
iss: decoded.iss,
|
|
108
|
+
exp: decoded.exp ? new Date(decoded.exp * 1000).toISOString() : undefined,
|
|
109
|
+
iat: decoded.iat ? new Date(decoded.iat * 1000).toISOString() : undefined,
|
|
110
|
+
} : undefined,
|
|
111
|
+
});
|
|
112
|
+
},
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
//# sourceMappingURL=createJwtAuthTool.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createJwtAuthTool.js","sourceRoot":"","sources":["../src/createJwtAuthTool.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vurb/jwt — JWT Verification for MCP Servers
|
|
3
|
+
*
|
|
4
|
+
* Standards-compliant JWT verification with jose integration,
|
|
5
|
+
* native HS256 fallback, and vurb middleware.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { JwtVerifier, requireJwt, createJwtAuthTool } from '@vurb/jwt';
|
|
10
|
+
*
|
|
11
|
+
* // Middleware
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireJwt({ secret: 'my-secret' }))
|
|
14
|
+
* .action({ name: 'list', handler: async () => success([]) });
|
|
15
|
+
*
|
|
16
|
+
* // Standalone
|
|
17
|
+
* const verifier = new JwtVerifier({ secret: 'my-secret' });
|
|
18
|
+
* const payload = await verifier.verify(token);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module @vurb/jwt
|
|
22
|
+
* @author Vinkius Labs
|
|
23
|
+
* @license Apache-2.0
|
|
24
|
+
*/
|
|
25
|
+
export { JwtVerifier } from './JwtVerifier.js';
|
|
26
|
+
export type { JwtVerifierConfig, JwtPayload, JwtVerifyResult, } from './JwtVerifier.js';
|
|
27
|
+
export { createJwtAuthTool } from './createJwtAuthTool.js';
|
|
28
|
+
export type { JwtAuthToolConfig } from './createJwtAuthTool.js';
|
|
29
|
+
export { requireJwt } from './middleware.js';
|
|
30
|
+
export type { RequireJwtOptions } from './middleware.js';
|
|
31
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,YAAY,EACR,iBAAiB,EACjB,UAAU,EACV,eAAe,GAClB,MAAM,kBAAkB,CAAC;AAE1B,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,YAAY,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAEhE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @vurb/jwt — JWT Verification for MCP Servers
|
|
3
|
+
*
|
|
4
|
+
* Standards-compliant JWT verification with jose integration,
|
|
5
|
+
* native HS256 fallback, and vurb middleware.
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* ```ts
|
|
9
|
+
* import { JwtVerifier, requireJwt, createJwtAuthTool } from '@vurb/jwt';
|
|
10
|
+
*
|
|
11
|
+
* // Middleware
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireJwt({ secret: 'my-secret' }))
|
|
14
|
+
* .action({ name: 'list', handler: async () => success([]) });
|
|
15
|
+
*
|
|
16
|
+
* // Standalone
|
|
17
|
+
* const verifier = new JwtVerifier({ secret: 'my-secret' });
|
|
18
|
+
* const payload = await verifier.verify(token);
|
|
19
|
+
* ```
|
|
20
|
+
*
|
|
21
|
+
* @module @vurb/jwt
|
|
22
|
+
* @author Vinkius Labs
|
|
23
|
+
* @license Apache-2.0
|
|
24
|
+
*/
|
|
25
|
+
export { JwtVerifier } from './JwtVerifier.js';
|
|
26
|
+
export { createJwtAuthTool } from './createJwtAuthTool.js';
|
|
27
|
+
export { requireJwt } from './middleware.js';
|
|
28
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAO/C,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Auth Middleware — requireJwt()
|
|
3
|
+
*
|
|
4
|
+
* vurb middleware that ensures requests carry a valid JWT.
|
|
5
|
+
* Extracts the token, verifies it via JwtVerifier, and rejects
|
|
6
|
+
* invalid/expired/missing tokens with self-healing error responses.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { requireJwt } from '@vurb/jwt';
|
|
11
|
+
*
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireJwt({ secret: process.env.JWT_SECRET! }))
|
|
14
|
+
* .action({ name: 'list', handler: async (ctx) => { ... } });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import type { ToolResponse } from 'vurb';
|
|
18
|
+
import type { JwtVerifierConfig, JwtPayload } from './JwtVerifier.js';
|
|
19
|
+
export interface RequireJwtOptions extends JwtVerifierConfig {
|
|
20
|
+
/**
|
|
21
|
+
* Custom function to extract the JWT from the context.
|
|
22
|
+
* Default: checks `ctx.token`, `ctx.jwt`, `ctx.headers.authorization` (Bearer).
|
|
23
|
+
*/
|
|
24
|
+
readonly extractToken?: (ctx: unknown) => string | null | undefined;
|
|
25
|
+
/**
|
|
26
|
+
* Callback invoked after successful verification.
|
|
27
|
+
* Use this to inject the decoded payload into the context.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* requireJwt({
|
|
32
|
+
* secret: 'my-secret',
|
|
33
|
+
* onVerified: (ctx, payload) => {
|
|
34
|
+
* (ctx as any).userId = payload.sub;
|
|
35
|
+
* },
|
|
36
|
+
* });
|
|
37
|
+
* ```
|
|
38
|
+
*/
|
|
39
|
+
readonly onVerified?: (ctx: unknown, payload: JwtPayload) => void;
|
|
40
|
+
/** Error code for toolError response. Default: 'JWT_INVALID' */
|
|
41
|
+
readonly errorCode?: string;
|
|
42
|
+
/** Recovery hint for the LLM. Default: 'Provide a valid JWT in the authorization context' */
|
|
43
|
+
readonly recoveryHint?: string;
|
|
44
|
+
/** Recovery action name. Default: 'auth' */
|
|
45
|
+
readonly recoveryAction?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Creates a vurb middleware that verifies JWTs.
|
|
49
|
+
*
|
|
50
|
+
* Returns `toolError('JWT_INVALID')` with self-healing hints
|
|
51
|
+
* when no valid JWT is found.
|
|
52
|
+
*/
|
|
53
|
+
export declare function requireJwt(options: RequireJwtOptions): (ctx: unknown, _args: Record<string, unknown>, next: () => Promise<ToolResponse>) => Promise<ToolResponse>;
|
|
54
|
+
//# sourceMappingURL=middleware.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.d.ts","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,MAAM,CAAC;AAEzC,OAAO,KAAK,EAAE,iBAAiB,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAMtE,MAAM,WAAW,iBAAkB,SAAQ,iBAAiB;IACxD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,MAAM,GAAG,IAAI,GAAG,SAAS,CAAC;IAEpE;;;;;;;;;;;;;OAaG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,GAAG,EAAE,OAAO,EAAE,OAAO,EAAE,UAAU,KAAK,IAAI,CAAC;IAElE,gEAAgE;IAChE,QAAQ,CAAC,SAAS,CAAC,EAAE,MAAM,CAAC;IAE5B,6FAA6F;IAC7F,QAAQ,CAAC,YAAY,CAAC,EAAE,MAAM,CAAC;IAE/B,4CAA4C;IAC5C,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAC;CACpC;AAMD;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,OAAO,EAAE,iBAAiB,IAQnC,KAAK,OAAO,EAAE,OAAO,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,MAAM,MAAM,OAAO,CAAC,YAAY,CAAC,KAAG,OAAO,CAAC,YAAY,CAAC,CA8BxH"}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* JWT Auth Middleware — requireJwt()
|
|
3
|
+
*
|
|
4
|
+
* vurb middleware that ensures requests carry a valid JWT.
|
|
5
|
+
* Extracts the token, verifies it via JwtVerifier, and rejects
|
|
6
|
+
* invalid/expired/missing tokens with self-healing error responses.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```ts
|
|
10
|
+
* import { requireJwt } from '@vurb/jwt';
|
|
11
|
+
*
|
|
12
|
+
* const projects = createTool('projects')
|
|
13
|
+
* .use(requireJwt({ secret: process.env.JWT_SECRET! }))
|
|
14
|
+
* .action({ name: 'list', handler: async (ctx) => { ... } });
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
import { toolError } from 'vurb';
|
|
18
|
+
import { JwtVerifier } from './JwtVerifier.js';
|
|
19
|
+
// ============================================================================
|
|
20
|
+
// Middleware Factory
|
|
21
|
+
// ============================================================================
|
|
22
|
+
/**
|
|
23
|
+
* Creates a vurb middleware that verifies JWTs.
|
|
24
|
+
*
|
|
25
|
+
* Returns `toolError('JWT_INVALID')` with self-healing hints
|
|
26
|
+
* when no valid JWT is found.
|
|
27
|
+
*/
|
|
28
|
+
export function requireJwt(options) {
|
|
29
|
+
const verifier = new JwtVerifier(options);
|
|
30
|
+
const extractToken = options.extractToken ?? defaultExtractToken;
|
|
31
|
+
const errorCode = options.errorCode ?? 'JWT_INVALID';
|
|
32
|
+
const recoveryHint = options.recoveryHint ?? 'Provide a valid JWT in the authorization context';
|
|
33
|
+
const recoveryAction = options.recoveryAction ?? 'auth';
|
|
34
|
+
const onVerified = options.onVerified;
|
|
35
|
+
return async (ctx, _args, next) => {
|
|
36
|
+
const raw = extractToken(ctx);
|
|
37
|
+
if (!raw) {
|
|
38
|
+
return toolError(errorCode, {
|
|
39
|
+
message: 'JWT authentication required',
|
|
40
|
+
suggestion: recoveryHint,
|
|
41
|
+
availableActions: [recoveryAction],
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Strip "Bearer " prefix if present
|
|
45
|
+
const token = raw.startsWith('Bearer ') ? raw.slice(7) : raw;
|
|
46
|
+
const result = await verifier.verifyDetailed(token);
|
|
47
|
+
if (!result.valid) {
|
|
48
|
+
return toolError(errorCode, {
|
|
49
|
+
message: `JWT verification failed: ${result.reason}`,
|
|
50
|
+
suggestion: recoveryHint,
|
|
51
|
+
availableActions: [recoveryAction],
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
if (onVerified && result.payload) {
|
|
55
|
+
onVerified(ctx, result.payload);
|
|
56
|
+
}
|
|
57
|
+
return next();
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
// ============================================================================
|
|
61
|
+
// Default Token Extractor
|
|
62
|
+
// ============================================================================
|
|
63
|
+
/**
|
|
64
|
+
* Default JWT extractor — checks common patterns:
|
|
65
|
+
* - `ctx.token`
|
|
66
|
+
* - `ctx.jwt`
|
|
67
|
+
* - `ctx.headers.authorization` (Bearer prefix)
|
|
68
|
+
*/
|
|
69
|
+
function defaultExtractToken(ctx) {
|
|
70
|
+
if (!ctx || typeof ctx !== 'object')
|
|
71
|
+
return null;
|
|
72
|
+
const obj = ctx;
|
|
73
|
+
// Direct token property
|
|
74
|
+
if (typeof obj['token'] === 'string' && obj['token']) {
|
|
75
|
+
return obj['token'];
|
|
76
|
+
}
|
|
77
|
+
// JWT property
|
|
78
|
+
if (typeof obj['jwt'] === 'string' && obj['jwt']) {
|
|
79
|
+
return obj['jwt'];
|
|
80
|
+
}
|
|
81
|
+
// Authorization header
|
|
82
|
+
const headers = obj['headers'];
|
|
83
|
+
if (headers) {
|
|
84
|
+
const auth = headers['authorization'];
|
|
85
|
+
if (typeof auth === 'string' && auth) {
|
|
86
|
+
return auth;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
//# sourceMappingURL=middleware.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"middleware.js","sourceRoot":"","sources":["../src/middleware.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@vurb/jwt",
|
|
3
|
+
"version": "3.1.31",
|
|
4
|
+
"description": "JWT verification middleware for MCP servers built with vurb. Standards-compliant token validation with jose, JWKS support, and self-healing error responses.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"scripts": {
|
|
15
|
+
"build": "tsc",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
18
|
+
},
|
|
19
|
+
"keywords": [
|
|
20
|
+
"mcp",
|
|
21
|
+
"vurb",
|
|
22
|
+
"jwt",
|
|
23
|
+
"json-web-token",
|
|
24
|
+
"authentication",
|
|
25
|
+
"middleware",
|
|
26
|
+
"ai",
|
|
27
|
+
"llm"
|
|
28
|
+
],
|
|
29
|
+
"author": "Renato Marinho",
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "git+https://github.com/vinkius-labs/vurb.ts.git",
|
|
33
|
+
"directory": "packages/jwt"
|
|
34
|
+
},
|
|
35
|
+
"bugs": {
|
|
36
|
+
"url": "https://github.com/vinkius-labs/vurb.ts/issues"
|
|
37
|
+
},
|
|
38
|
+
"homepage": "https://vurb.vinkius.com/",
|
|
39
|
+
"files": [
|
|
40
|
+
"dist",
|
|
41
|
+
"README.md"
|
|
42
|
+
],
|
|
43
|
+
"engines": {
|
|
44
|
+
"node": ">=18.0.0"
|
|
45
|
+
},
|
|
46
|
+
"publishConfig": {
|
|
47
|
+
"access": "public"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@vurb/core": "^3.0.0",
|
|
51
|
+
"jose": "^5.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependenciesMeta": {
|
|
54
|
+
"jose": {
|
|
55
|
+
"optional": true
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
"devDependencies": {
|
|
59
|
+
"@vurb/core": ">=3.0.0"
|
|
60
|
+
},
|
|
61
|
+
"license": "Apache-2.0"
|
|
62
|
+
}
|