@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
|
|
1
|
+
{"version":3,"file":"authMiddleware.js","sourceRoot":"","sources":["../../src/lib/authMiddleware.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;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,CAAA;YAEF,2CAA2C;YAC3C,6DAA6D;YAC7D,yEAAyE;YACzE,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAA;YACvB,IAAI,GAAG,EAAE,CAAC;gBACR,MAAM,SAAS,GAAG,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAA;gBAClD,MAAM,MAAM,GAAG,GAAG,CAAC,GAAG,CAAC,mBAAmB,CAAC,IAAI,GAAG,CAAC,QAAQ,CAAA;gBAC3D,MAAM,IAAI,GAAG,GAAG,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;gBAC5B,MAAM,WAAW,GAAG,GAAG,MAAM,MAAM,IAAI,MAAM,CAAA;gBAE7C,uCAAuC;gBACvC,MAAM,eAAe,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE;oBACzC,yDAAyD;oBACzD,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,EAAE,CAAC;wBACnF,OAAO,CAAC,KAAK,WAAW,CAAA;oBAC1B,CAAC;oBACD,iFAAiF;oBACjF,OAAO,IAAI,CAAA;gBACb,CAAC,CAAC,CAAA;gBAEF,IAAI,CAAC,eAAe,EAAE,CAAC;oBACrB,MAAM,CAAC,KAAK,CAAC,iCAAiC,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,CAAC,CAAA;oBAC/E,OAAO,YAAY,CAAC,GAAG,EAAE,GAAG,EAAE,IAAI,EAAE,kCAAkC,CAAC,CAAA;gBACzE,CAAC;YACH,CAAC;YAED,qDAAqD;YACrD,CAAC;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"}
|
package/package.json
CHANGED
|
@@ -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()
|