@silkweave/auth 2.6.1 → 3.0.0
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 +46 -9
- package/build/index.d.mts +4 -151
- package/build/index.d.mts.map +1 -1
- package/build/index.mjs +22 -624
- package/build/index.mjs.map +1 -1
- package/build/oauth.d.mts +112 -0
- package/build/oauth.d.mts.map +1 -0
- package/build/oauth.mjs +621 -0
- package/build/oauth.mjs.map +1 -0
- package/build/types-BFhAEzlP.d.mts +57 -0
- package/build/types-BFhAEzlP.d.mts.map +1 -0
- package/package.json +11 -4
package/README.md
CHANGED
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
# @silkweave/auth
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Auth for [Silkweave](https://github.com/silkweave/silkweave) MCP servers, in two layers:
|
|
4
|
+
|
|
5
|
+
- **Resource-server core** (the package root, `@silkweave/auth`) - the spec-required surface for an MCP server: bearer-token validation + protected resource metadata (RFC 9728). `jose`-only. Most servers need only this and delegate token issuance to an external IdP (Auth0/WorkOS/Stytch/Keycloak/Google).
|
|
6
|
+
- **OAuth authorization server** (the opt-in subpath, `@silkweave/auth/oauth`) - a full OAuth 2.1 proxy between MCP clients and an upstream IdP: PKCE, refresh tokens, dynamic client registration (CIMD), and persistence stores. Import this only when you want Silkweave itself to *be* the authorization server, so a pure resource server never pulls the issuer machinery into its dependency graph.
|
|
4
7
|
|
|
5
8
|
## Install
|
|
6
9
|
|
|
@@ -8,12 +11,14 @@ OAuth 2.1 authentication for [Silkweave](https://github.com/silkweave/silkweave)
|
|
|
8
11
|
pnpm add @silkweave/auth
|
|
9
12
|
```
|
|
10
13
|
|
|
14
|
+
> The OAuth-proxy helpers (`google`, `createOAuthProxy`, `createJsonStore`/`createRedisStore`/`createMemoryStore`, `OAuthStore`) are imported from `@silkweave/auth/oauth`. Bearer validation (`validateToken`) and `generateProtectedResourceMetadata` - plus the `AuthConfig`/`AuthInfo`/`OAuthRequest`/`OAuthResponse`/`OAuthProvider` types - live at the package root.
|
|
15
|
+
|
|
11
16
|
## Quick Start with Google OAuth
|
|
12
17
|
|
|
13
18
|
The `google()` helper returns a complete `AuthConfig` ready to pass to the `http()` adapter:
|
|
14
19
|
|
|
15
20
|
```typescript
|
|
16
|
-
import { google, createJsonStore } from '@silkweave/auth'
|
|
21
|
+
import { google, createJsonStore } from '@silkweave/auth/oauth'
|
|
17
22
|
import { silkweave } from '@silkweave/core'
|
|
18
23
|
import { http } from '@silkweave/mcp'
|
|
19
24
|
|
|
@@ -52,7 +57,7 @@ await silkweave({ name: 'my-server', description: 'My MCP Server', version: '1.0
|
|
|
52
57
|
For providers other than Google, use `createOAuthProxy()` directly. It returns an `OAuthProvider` that implements the full OAuth 2.1 proxy flow:
|
|
53
58
|
|
|
54
59
|
```typescript
|
|
55
|
-
import { createOAuthProxy } from '@silkweave/auth'
|
|
60
|
+
import { createOAuthProxy } from '@silkweave/auth/oauth'
|
|
56
61
|
|
|
57
62
|
const provider = createOAuthProxy({
|
|
58
63
|
authorizeUrl: 'https://provider.example.com/authorize',
|
|
@@ -139,6 +144,36 @@ if (result.error) {
|
|
|
139
144
|
console.log(result.auth?.clientId)
|
|
140
145
|
```
|
|
141
146
|
|
|
147
|
+
### Recommended: front with an external IdP (resource-server only)
|
|
148
|
+
|
|
149
|
+
The MCP 2026 spec direction is for servers to be **pure OAuth resource servers**
|
|
150
|
+
that delegate token issuance to a dedicated authorization server. For most
|
|
151
|
+
deployments, point `authorizationServers` at an external IdP (Auth0, WorkOS,
|
|
152
|
+
Stytch, Keycloak, Google, ...), have `verifyToken` validate that IdP's JWTs (e.g.
|
|
153
|
+
with `jose`'s `jwtVerify` against the IdP JWKS), and skip the `@silkweave/auth/oauth`
|
|
154
|
+
proxy entirely:
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
import { validateToken } from '@silkweave/auth'
|
|
158
|
+
|
|
159
|
+
const auth = {
|
|
160
|
+
resourceUrl: 'https://mcp.example.com',
|
|
161
|
+
authorizationServers: ['https://idp.example.com'],
|
|
162
|
+
issuer: 'https://idp.example.com', // RFC 9207 - bind the token to this issuer
|
|
163
|
+
audience: 'https://mcp.example.com', // RFC 8707 - reject tokens minted for another resource
|
|
164
|
+
requiredScopes: ['mcp:tools'], // advertised in the 403 step-up challenge (SEP-2350)
|
|
165
|
+
verifyToken: async (token) => {
|
|
166
|
+
const { payload } = await jwtVerify(token, JWKS, { issuer: 'https://idp.example.com', audience: 'https://mcp.example.com' })
|
|
167
|
+
return { token, clientId: payload.sub, scopes: String(payload.scope ?? '').split(' '), iss: payload.iss, aud: payload.aud, expiresAt: payload.exp }
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
`validateToken` then enforces, in order: expiry, **issuer** binding (`issuer`),
|
|
173
|
+
**audience** binding (`audience`, defaults to `resourceUrl`; set `false` to skip),
|
|
174
|
+
and **required scopes** - emitting a `WWW-Authenticate` challenge with `scope="..."`
|
|
175
|
+
on insufficient scope so the client knows what to step up to.
|
|
176
|
+
|
|
142
177
|
## Protected Resource Metadata (RFC 9728)
|
|
143
178
|
|
|
144
179
|
Generate the `/.well-known/oauth-protected-resource` document for your server:
|
|
@@ -148,11 +183,13 @@ import { generateProtectedResourceMetadata } from '@silkweave/auth'
|
|
|
148
183
|
|
|
149
184
|
const metadata = generateProtectedResourceMetadata(
|
|
150
185
|
'https://my-server.example.com',
|
|
151
|
-
['https://
|
|
186
|
+
['https://idp.example.com'],
|
|
187
|
+
['mcp:tools'] // optional scopes_supported - the adapters pass auth.requiredScopes here
|
|
152
188
|
)
|
|
153
189
|
// {
|
|
154
190
|
// resource: 'https://my-server.example.com',
|
|
155
|
-
// authorization_servers: ['https://
|
|
191
|
+
// authorization_servers: ['https://idp.example.com'],
|
|
192
|
+
// scopes_supported: ['mcp:tools'],
|
|
156
193
|
// bearer_methods_supported: ['header']
|
|
157
194
|
// }
|
|
158
195
|
```
|
|
@@ -166,7 +203,7 @@ The OAuth proxy needs persistent storage for auth codes, client registrations, P
|
|
|
166
203
|
In-memory storage -- data is lost on restart. Good for development:
|
|
167
204
|
|
|
168
205
|
```typescript
|
|
169
|
-
import { createMemoryStore } from '@silkweave/auth'
|
|
206
|
+
import { createMemoryStore } from '@silkweave/auth/oauth'
|
|
170
207
|
|
|
171
208
|
const store = createMemoryStore()
|
|
172
209
|
```
|
|
@@ -176,7 +213,7 @@ const store = createMemoryStore()
|
|
|
176
213
|
Persists to a JSON file on disk. Suitable for single-process deployments:
|
|
177
214
|
|
|
178
215
|
```typescript
|
|
179
|
-
import { createJsonStore } from '@silkweave/auth'
|
|
216
|
+
import { createJsonStore } from '@silkweave/auth/oauth'
|
|
180
217
|
|
|
181
218
|
const store = createJsonStore('oauth-store.json')
|
|
182
219
|
```
|
|
@@ -186,7 +223,7 @@ const store = createJsonStore('oauth-store.json')
|
|
|
186
223
|
For production multi-process deployments. Accepts any Redis client with `get`/`set`/`del` methods (compatible with `ioredis` and `redis`):
|
|
187
224
|
|
|
188
225
|
```typescript
|
|
189
|
-
import { createRedisStore } from '@silkweave/auth'
|
|
226
|
+
import { createRedisStore } from '@silkweave/auth/oauth'
|
|
190
227
|
import Redis from 'ioredis'
|
|
191
228
|
|
|
192
229
|
const store = createRedisStore({
|
|
@@ -200,7 +237,7 @@ const store = createRedisStore({
|
|
|
200
237
|
Implement the `OAuthStore` interface for your own backend:
|
|
201
238
|
|
|
202
239
|
```typescript
|
|
203
|
-
import type { OAuthStore } from '@silkweave/auth'
|
|
240
|
+
import type { OAuthStore } from '@silkweave/auth/oauth'
|
|
204
241
|
|
|
205
242
|
const store: OAuthStore = {
|
|
206
243
|
saveAuthCode(code, data) { /* ... */ },
|
package/build/index.d.mts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { a as OAuthRequest, i as OAuthProvider, n as VerifyToken, o as OAuthResponse, r as AuthInfo, t as AuthConfig } from "./types-BFhAEzlP.mjs";
|
|
1
2
|
import { SilkweaveContext } from "@silkweave/core";
|
|
2
3
|
|
|
3
4
|
//#region src/errors.d.ts
|
|
@@ -11,7 +12,7 @@ declare function insufficientScope(message?: string): AuthError;
|
|
|
11
12
|
//#endregion
|
|
12
13
|
//#region src/extract.d.ts
|
|
13
14
|
declare function extractBearerToken(header: string | null | undefined): string | null;
|
|
14
|
-
declare function buildWWWAuthenticate(error?: string, description?: string, resourceMetadataUrl?: string): string;
|
|
15
|
+
declare function buildWWWAuthenticate(error?: string, description?: string, resourceMetadataUrl?: string, scope?: string): string;
|
|
15
16
|
//#endregion
|
|
16
17
|
//#region src/metadata.d.ts
|
|
17
18
|
interface ProtectedResourceMetadata {
|
|
@@ -20,155 +21,7 @@ interface ProtectedResourceMetadata {
|
|
|
20
21
|
scopes_supported?: string[];
|
|
21
22
|
bearer_methods_supported?: string[];
|
|
22
23
|
}
|
|
23
|
-
declare function generateProtectedResourceMetadata(resourceUrl: string, authorizationServers: string[]): ProtectedResourceMetadata;
|
|
24
|
-
//#endregion
|
|
25
|
-
//#region src/provider/types.d.ts
|
|
26
|
-
interface AuthInfo {
|
|
27
|
-
token: string;
|
|
28
|
-
clientId?: string;
|
|
29
|
-
scopes?: string[];
|
|
30
|
-
expiresAt?: number;
|
|
31
|
-
[key: string]: unknown;
|
|
32
|
-
}
|
|
33
|
-
interface OAuthRequest {
|
|
34
|
-
method: string;
|
|
35
|
-
url: URL;
|
|
36
|
-
headers: Record<string, string | undefined>;
|
|
37
|
-
body?: Record<string, string>;
|
|
38
|
-
}
|
|
39
|
-
interface OAuthResponse {
|
|
40
|
-
status: number;
|
|
41
|
-
headers: Record<string, string>;
|
|
42
|
-
body?: string | Record<string, unknown>;
|
|
43
|
-
}
|
|
44
|
-
interface OAuthProvider {
|
|
45
|
-
authorize(req: OAuthRequest): Promise<OAuthResponse>;
|
|
46
|
-
callback(req: OAuthRequest): Promise<OAuthResponse>;
|
|
47
|
-
token(req: OAuthRequest): Promise<OAuthResponse>;
|
|
48
|
-
register(req: OAuthRequest): Promise<OAuthResponse>;
|
|
49
|
-
metadata(): OAuthResponse;
|
|
50
|
-
verifyToken(token: string): Promise<AuthInfo | undefined>;
|
|
51
|
-
}
|
|
52
|
-
//#endregion
|
|
53
|
-
//#region src/types.d.ts
|
|
54
|
-
type VerifyToken = (token: string, context: SilkweaveContext) => Promise<AuthInfo | undefined>;
|
|
55
|
-
interface AuthConfig {
|
|
56
|
-
verifyToken: VerifyToken;
|
|
57
|
-
required?: boolean;
|
|
58
|
-
resourceUrl?: string;
|
|
59
|
-
authorizationServers?: string[];
|
|
60
|
-
requiredScopes?: string[];
|
|
61
|
-
provider?: OAuthProvider;
|
|
62
|
-
callbackPath?: string;
|
|
63
|
-
}
|
|
64
|
-
//#endregion
|
|
65
|
-
//#region src/provider/store.d.ts
|
|
66
|
-
interface AuthCodeData {
|
|
67
|
-
clientId: string;
|
|
68
|
-
redirectUri: string;
|
|
69
|
-
codeChallenge: string;
|
|
70
|
-
codeChallengeMethod: string;
|
|
71
|
-
scopes: string[];
|
|
72
|
-
upstreamAccessToken: string;
|
|
73
|
-
upstreamIdToken?: string;
|
|
74
|
-
email?: string;
|
|
75
|
-
sub?: string;
|
|
76
|
-
expiresAt: number;
|
|
77
|
-
}
|
|
78
|
-
interface PendingAuthData {
|
|
79
|
-
clientId: string;
|
|
80
|
-
redirectUri: string;
|
|
81
|
-
codeChallenge: string;
|
|
82
|
-
codeChallengeMethod: string;
|
|
83
|
-
scope: string;
|
|
84
|
-
clientState: string;
|
|
85
|
-
resource?: string;
|
|
86
|
-
expiresAt: number;
|
|
87
|
-
}
|
|
88
|
-
interface ClientRegistration {
|
|
89
|
-
clientId: string;
|
|
90
|
-
clientSecret: string;
|
|
91
|
-
redirectUris: string[];
|
|
92
|
-
clientName?: string;
|
|
93
|
-
createdAt: number;
|
|
94
|
-
}
|
|
95
|
-
interface RefreshTokenData {
|
|
96
|
-
clientId: string;
|
|
97
|
-
scopes: string[];
|
|
98
|
-
email?: string;
|
|
99
|
-
sub?: string;
|
|
100
|
-
expiresAt: number;
|
|
101
|
-
}
|
|
102
|
-
interface OAuthStore {
|
|
103
|
-
saveAuthCode(code: string, data: AuthCodeData): Promise<void>;
|
|
104
|
-
getAuthCode(code: string): Promise<AuthCodeData | undefined>;
|
|
105
|
-
deleteAuthCode(code: string): Promise<void>;
|
|
106
|
-
savePendingAuth(state: string, data: PendingAuthData): Promise<void>;
|
|
107
|
-
getPendingAuth(state: string): Promise<PendingAuthData | undefined>;
|
|
108
|
-
deletePendingAuth(state: string): Promise<void>;
|
|
109
|
-
savePkceVerifier(state: string, verifier: string): Promise<void>;
|
|
110
|
-
getPkceVerifier(state: string): Promise<string | undefined>;
|
|
111
|
-
deletePkceVerifier(state: string): Promise<void>;
|
|
112
|
-
saveClient(clientId: string, data: ClientRegistration): Promise<void>;
|
|
113
|
-
getClient(clientId: string): Promise<ClientRegistration | undefined>;
|
|
114
|
-
saveRefreshToken(token: string, data: RefreshTokenData): Promise<void>;
|
|
115
|
-
getRefreshToken(token: string): Promise<RefreshTokenData | undefined>;
|
|
116
|
-
deleteRefreshToken(token: string): Promise<void>;
|
|
117
|
-
}
|
|
118
|
-
//#endregion
|
|
119
|
-
//#region src/provider/google.d.ts
|
|
120
|
-
interface GoogleOAuthOptions {
|
|
121
|
-
clientId: string;
|
|
122
|
-
clientSecret: string;
|
|
123
|
-
resourceUrl: string;
|
|
124
|
-
redirectUris: string[];
|
|
125
|
-
requiredScopes?: string[];
|
|
126
|
-
callbackPath?: string;
|
|
127
|
-
signingKey?: string;
|
|
128
|
-
tokenTtl?: number;
|
|
129
|
-
store?: OAuthStore;
|
|
130
|
-
}
|
|
131
|
-
declare function google(options: GoogleOAuthOptions): AuthConfig;
|
|
132
|
-
//#endregion
|
|
133
|
-
//#region src/provider/proxy.d.ts
|
|
134
|
-
interface OAuthProxyConfig {
|
|
135
|
-
authorizeUrl: string;
|
|
136
|
-
tokenUrl: string;
|
|
137
|
-
userinfoUrl?: string;
|
|
138
|
-
clientId: string;
|
|
139
|
-
clientSecret: string;
|
|
140
|
-
resourceUrl: string;
|
|
141
|
-
redirectUris: string[];
|
|
142
|
-
requiredScopes?: string[];
|
|
143
|
-
callbackPath?: string;
|
|
144
|
-
signingKey?: string;
|
|
145
|
-
tokenTtl?: number;
|
|
146
|
-
store?: OAuthStore;
|
|
147
|
-
}
|
|
148
|
-
declare function createOAuthProxy(config: OAuthProxyConfig): OAuthProvider;
|
|
149
|
-
//#endregion
|
|
150
|
-
//#region src/provider/uri.d.ts
|
|
151
|
-
declare function matchRedirectUri(uri: string, patterns: string[]): boolean;
|
|
152
|
-
//#endregion
|
|
153
|
-
//#region src/store/json-store.d.ts
|
|
154
|
-
declare function createJsonStore(path: string): OAuthStore;
|
|
155
|
-
//#endregion
|
|
156
|
-
//#region src/store/memory-store.d.ts
|
|
157
|
-
declare function createMemoryStore(): OAuthStore;
|
|
158
|
-
//#endregion
|
|
159
|
-
//#region src/store/redis-store.d.ts
|
|
160
|
-
interface RedisClient {
|
|
161
|
-
get(key: string): Promise<unknown>;
|
|
162
|
-
set(key: string, value: string, options?: {
|
|
163
|
-
ex?: number;
|
|
164
|
-
}): Promise<unknown>;
|
|
165
|
-
del(key: string): Promise<unknown>;
|
|
166
|
-
}
|
|
167
|
-
interface RedisStoreOptions {
|
|
168
|
-
client: RedisClient;
|
|
169
|
-
prefix?: string;
|
|
170
|
-
}
|
|
171
|
-
declare function createRedisStore(options: RedisStoreOptions): OAuthStore;
|
|
24
|
+
declare function generateProtectedResourceMetadata(resourceUrl: string, authorizationServers: string[], scopesSupported?: string[]): ProtectedResourceMetadata;
|
|
172
25
|
//#endregion
|
|
173
26
|
//#region src/validate.d.ts
|
|
174
27
|
interface ValidateResult {
|
|
@@ -184,5 +37,5 @@ interface ValidateResult {
|
|
|
184
37
|
}
|
|
185
38
|
declare function validateToken(authorizationHeader: string | null | undefined, config: AuthConfig, context: SilkweaveContext): Promise<ValidateResult>;
|
|
186
39
|
//#endregion
|
|
187
|
-
export {
|
|
40
|
+
export { AuthConfig, AuthError, AuthInfo, type OAuthProvider, type OAuthRequest, type OAuthResponse, ProtectedResourceMetadata, ValidateResult, VerifyToken, buildWWWAuthenticate, extractBearerToken, generateProtectedResourceMetadata, insufficientScope, invalidToken, validateToken };
|
|
188
41
|
//# sourceMappingURL=index.d.mts.map
|
package/build/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/extract.ts","../src/metadata.ts","../src/
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/errors.ts","../src/extract.ts","../src/metadata.ts","../src/validate.ts"],"mappings":";;;;cAAa,SAAA,SAAkB,KAAA;EAAA,SAGX,IAAA;EAAA,SACA,UAAA;cAFhB,OAAA,UACgB,IAAA,UACA,UAAA;AAAA;AAAA,iBAOJ,YAAA,CAAa,OAAA,YAAyB,SAAA;AAAA,iBAItC,iBAAA,CAAkB,OAAA,YAA8B,SAAA;;;iBCfhD,kBAAA,CAAmB,MAAA;AAAA,iBAMnB,oBAAA,CACd,KAAA,WACA,WAAA,WACA,mBAAA,WACA,KAAA;;;UCVe,yBAAA;EACf,QAAA;EACA,qBAAA;EACA,gBAAA;EACA,wBAAA;AAAA;AAAA,iBAGc,iCAAA,CACd,WAAA,UACA,oBAAA,YACA,eAAA,cACC,yBAAA;;;UCNc,cAAA;EACf,IAAA,GAAO,QAAA;EACP,KAAA;IACE,UAAA;IACA,OAAA,EAAS,MAAA;IACT,IAAA;MAAQ,KAAA;MAAe,iBAAA;IAAA;EAAA;AAAA;AAAA,iBAIL,aAAA,CACpB,mBAAA,6BACA,MAAA,EAAQ,UAAA,EACR,OAAA,EAAS,gBAAA,GACR,OAAA,CAAQ,cAAA"}
|