@platf/bridge 0.0.28 → 0.0.29

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 CHANGED
@@ -15,6 +15,7 @@ Wraps any MCP server that speaks JSON-RPC over **stdio** and exposes it as a **S
15
15
 
16
16
  The bridge supports OAuth 2.0 JWT authentication via `--authIssuer` and `--authClientId`.
17
17
  - Validates **Bearer tokens** (signature + expiration checks against issuer's JWKS).
18
+ - Validates **audience claim** (RFC 9068): URL audiences must match the bridge's resource URL.
18
19
  - Exposes standard discovery endpoints:
19
20
  - `/.well-known/oauth-protected-resource` (RFC 9728)
20
21
  - `/.well-known/oauth-authorization-server` (RFC 8414 proxy)
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * - Fetches JWKS from `{issuer}/jwks` using jose's createRemoteJWKSet (auto-caches).
5
5
  * - Validates Bearer token: signature, `iss`, `exp`.
6
+ * - Validates `aud` claim per RFC 9068: must be resource URL or client ID.
6
7
  * - On failure returns 401 with RFC 9728–compliant `WWW-Authenticate` header
7
8
  * pointing at the OAuth Protected Resource Metadata document.
8
9
  */
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * - Fetches JWKS from `{issuer}/jwks` using jose's createRemoteJWKSet (auto-caches).
5
5
  * - Validates Bearer token: signature, `iss`, `exp`.
6
+ * - Validates `aud` claim per RFC 9068: must be resource URL or client ID.
6
7
  * - On failure returns 401 with RFC 9728–compliant `WWW-Authenticate` header
7
8
  * pointing at the OAuth Protected Resource Metadata document.
8
9
  */
@@ -21,6 +22,31 @@ export function createAuthMiddleware(auth, logger) {
21
22
  const { payload } = await jwtVerify(token, JWKS, {
22
23
  issuer: auth.issuer,
23
24
  });
25
+ // ── RFC 9068 §2.2: Audience Validation ──
26
+ // If aud is a URL, it must match this resource server's URL.
27
+ // If aud is a client ID (non-URL), accept it for backward compatibility.
28
+ const aud = payload.aud;
29
+ if (aud) {
30
+ const audValues = Array.isArray(aud) ? aud : [aud];
31
+ const scheme = req.get('x-forwarded-proto') || req.protocol;
32
+ const host = req.get('host');
33
+ const resourceUrl = `${scheme}://${host}/mcp`;
34
+ // Check if any audience value is valid
35
+ const isValidAudience = audValues.some(a => {
36
+ // If it looks like a URL, it must match our resource URL
37
+ if (typeof a === 'string' && (a.startsWith('http://') || a.startsWith('https://'))) {
38
+ return a === resourceUrl;
39
+ }
40
+ // Non-URL audience (client ID) - accept for backward compat with user OAuth flow
41
+ return true;
42
+ });
43
+ if (!isValidAudience) {
44
+ logger.error('[auth] Token audience mismatch:', { aud, expected: resourceUrl });
45
+ return unauthorized(req, res, auth, 'invalid_token: audience mismatch');
46
+ }
47
+ }
48
+ // Attach token payload to request for downstream use
49
+ ;
24
50
  req.tokenPayload = payload;
25
51
  next();
26
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/lib/authMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,kBAAkB,EAAE,SAAS,EAAE,MAAM,MAAM,CAAA;AAIpD,iEAAiE;AACjE,MAAM,UAAU,oBAAoB,CAAC,IAAgB,EAAE,MAAc;IACnE,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,GAAG,IAAI,CAAC,MAAM,OAAO,CAAC,CAAA;IAC9C,MAAM,IAAI,GAAG,kBAAkB,CAAC,OAAO,CAAC,CAAA;IAExC,OAAO,KAAK,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,EAAE;QAC9B,MAAM,UAAU,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,CAAA;QAC5C,IAAI,CAAC,UAAU,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;YACrD,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,2CAA2C,CAAC,CAAA;QAClF,CAAC;QAED,MAAM,KAAK,GAAG,UAAU,CAAC,KAAK,CAAC,CAAC,CAAC,CAAA;QAEjC,IAAI,CAAC;YACH,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE;gBAC/C,MAAM,EAAE,IAAI,CAAC,MAAM;aACpB,CAAC,CAGD;YAAC,GAAW,CAAC,YAAY,GAAG,OAAO,CAAA;YACpC,IAAI,EAAE,CAAA;QACR,CAAC;QAAC,OAAO,GAAQ,EAAE,CAAC;YAClB,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,GAAG,CAAC,OAAO,IAAI,GAAG,CAAC,CAAA;YACnE,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,eAAe,CAAC,CAAA;QACtD,CAAC;IACH,CAAC,CAAA;AACH,CAAC;AAED,SAAS,YAAY,CACnB,GAA8B,EAC9B,GAA+B,EAC/B,IAAgB,EAChB,KAAa;IAEb,8DAA8D;IAC9D,MAAM,MAAM,GAAG,GAAG,CAAC,QAAQ,CAAA;IAC3B,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;IAC5B,MAAM,mBAAmB,GAAG,GAAG,MAAM,MAAM,IAAI,uCAAuC,CAAA;IAEtF,GAAG,CAAC,SAAS,CACX,kBAAkB,EAClB,8BAA8B,KAAK,yBAAyB,mBAAmB,GAAG,CACnF,CAAA;IACD,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,cAAc,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC,CAAA;AACjE,CAAC"}
1
+ {"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/lib/authMiddleware.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@platf/bridge",
3
- "version": "0.0.28",
3
+ "version": "0.0.29",
4
4
  "description": "Stdio-to-Streamable HTTP bridge for MCP servers — Platf AI Hub",
5
5
  "module": "src/index.ts",
6
6
  "main": "dist/index.js",
@@ -3,6 +3,7 @@
3
3
  *
4
4
  * - Fetches JWKS from `{issuer}/jwks` using jose's createRemoteJWKSet (auto-caches).
5
5
  * - Validates Bearer token: signature, `iss`, `exp`.
6
+ * - Validates `aud` claim per RFC 9068: must be resource URL or client ID.
6
7
  * - On failure returns 401 with RFC 9728–compliant `WWW-Authenticate` header
7
8
  * pointing at the OAuth Protected Resource Metadata document.
8
9
  */
@@ -29,6 +30,32 @@ export function createAuthMiddleware(auth: AuthConfig, logger: Logger): RequestH
29
30
  issuer: auth.issuer,
30
31
  })
31
32
 
33
+ // ── RFC 9068 §2.2: Audience Validation ──
34
+ // If aud is a URL, it must match this resource server's URL.
35
+ // If aud is a client ID (non-URL), accept it for backward compatibility.
36
+ const aud = payload.aud
37
+ if (aud) {
38
+ const audValues = Array.isArray(aud) ? aud : [aud]
39
+ const scheme = req.get('x-forwarded-proto') || req.protocol
40
+ const host = req.get('host')
41
+ const resourceUrl = `${scheme}://${host}/mcp`
42
+
43
+ // Check if any audience value is valid
44
+ const isValidAudience = audValues.some(a => {
45
+ // If it looks like a URL, it must match our resource URL
46
+ if (typeof a === 'string' && (a.startsWith('http://') || a.startsWith('https://'))) {
47
+ return a === resourceUrl
48
+ }
49
+ // Non-URL audience (client ID) - accept for backward compat with user OAuth flow
50
+ return true
51
+ })
52
+
53
+ if (!isValidAudience) {
54
+ logger.error('[auth] Token audience mismatch:', { aud, expected: resourceUrl })
55
+ return unauthorized(req, res, auth, 'invalid_token: audience mismatch')
56
+ }
57
+ }
58
+
32
59
  // Attach token payload to request for downstream use
33
60
  ;(req as any).tokenPayload = payload
34
61
  next()