Skip to content

Commit 02ce81b

Browse files
committed
simplify diff
1 parent 6656d23 commit 02ce81b

File tree

5 files changed

+20
-105
lines changed

5 files changed

+20
-105
lines changed

src/examples/server/demoInMemoryOAuthProvider.ts

Lines changed: 13 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@ import { OAuthClientInformationFull, OAuthMetadata, OAuthTokens } from '../../sh
55
import express, { Request, Response } from "express";
66
import { AuthInfo } from '../../server/auth/types.js';
77
import { createOAuthMetadata, mcpAuthRouter } from '../../server/auth/router.js';
8-
import { InvalidTargetError, InvalidRequestError } from '../../server/auth/errors.js';
98
import { resourceUrlFromServerUrl } from '../../shared/auth-utils.js';
109

1110

@@ -22,61 +21,28 @@ export class DemoInMemoryClientsStore implements OAuthRegisteredClientsStore {
2221
}
2322
}
2423

25-
/**
26-
* Configuration options for the demo OAuth provider
27-
*/
28-
export interface DemoOAuthProviderConfig {
29-
/**
30-
* The canonical URL of this MCP server. When provided, the provider will validate
31-
* that the resource parameter in OAuth requests matches this URL.
32-
*
33-
* This should be the full URL that clients use to connect to this server,
34-
* without any fragment component (e.g., "https://api.example.com/mcp").
35-
*
36-
* Required when validateResourceMatchesServer is true.
37-
*/
38-
serverUrl?: string;
39-
40-
/**
41-
* If true, validates that the resource parameter matches the configured serverUrl.
42-
*
43-
* When enabled:
44-
* - serverUrl must be configured (throws error if not)
45-
* - resource parameter is required on all requests
46-
* - resource must exactly match serverUrl (after fragment removal)
47-
* - requests without resource parameter will be rejected with invalid_request error
48-
* - requests with non-matching resource will be rejected with invalid_target error
49-
*
50-
* When disabled:
51-
* - warnings are logged when resource parameter is missing (for migration tracking)
52-
*
53-
* @default false
54-
*/
55-
validateResourceMatchesServer?: boolean;
56-
}
57-
5824
export class DemoInMemoryAuthProvider implements OAuthServerProvider {
5925
clientsStore = new DemoInMemoryClientsStore();
6026
private codes = new Map<string, {
6127
params: AuthorizationParams,
6228
client: OAuthClientInformationFull}>();
6329
private tokens = new Map<string, AuthInfo>();
64-
private config?: DemoOAuthProviderConfig;
65-
66-
constructor(config?: DemoOAuthProviderConfig) {
67-
if (config?.validateResourceMatchesServer && !config?.serverUrl) {
68-
throw new Error("serverUrl must be configured when validateResourceMatchesServer is true");
30+
private validateResource?: (resource?: URL) => boolean;
31+
32+
constructor(mcpServerUrl?: URL) {
33+
if (mcpServerUrl) {
34+
const expectedResource = resourceUrlFromServerUrl(mcpServerUrl);
35+
this.validateResource = (resource?: URL) => {
36+
return !resource || resource.toString() !== expectedResource.toString();
37+
};
6938
}
70-
this.config = config;
7139
}
7240

7341
async authorize(
7442
client: OAuthClientInformationFull,
7543
params: AuthorizationParams,
7644
res: Response
7745
): Promise<void> {
78-
await this.validateResource(params.resource);
79-
8046
const code = randomUUID();
8147

8248
const searchParams = new URLSearchParams({
@@ -126,7 +92,9 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
12692
throw new Error(`Authorization code was not issued to this client, ${codeData.client.client_id} != ${client.client_id}`);
12793
}
12894

129-
await this.validateResource(codeData.params.resource);
95+
if (this.validateResource && !this.validateResource(codeData.params.resource)) {
96+
throw new Error('Invalid resource');
97+
}
13098

13199
this.codes.delete(authorizationCode);
132100
const token = randomUUID();
@@ -164,7 +132,6 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
164132
if (!tokenData || !tokenData.expiresAt || tokenData.expiresAt < Date.now()) {
165133
throw new Error('Invalid or expired token');
166134
}
167-
await this.validateResource(tokenData.resource);
168135

169136
return {
170137
token,
@@ -175,29 +142,6 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
175142
};
176143
}
177144

178-
/**
179-
* Validates that the client is allowed to access the requested resource.
180-
* In a real implementation, this would check against a database or configuration.
181-
*/
182-
private async validateResource(resource?: string): Promise<void> {
183-
if (this.config?.validateResourceMatchesServer) {
184-
if (!resource) {
185-
throw new InvalidRequestError("Resource parameter is required when server URL validation is enabled");
186-
}
187-
188-
const canonicalServerUrl = resourceUrlFromServerUrl(this.config.serverUrl!);
189-
190-
if (resource !== canonicalServerUrl) {
191-
throw new InvalidTargetError(
192-
`Resource parameter '${resource}' does not match this server's URL '${canonicalServerUrl}'`
193-
);
194-
}
195-
} else if (!resource) {
196-
// Always log warning if resource is missing (unless validation is enabled)
197-
console.warn(`Token refresh request is missing the resource parameter. Consider migrating to RFC 8707.`);
198-
}
199-
}
200-
201145
/**
202146
* Get token details including resource information (for demo introspection endpoint)
203147
*/
@@ -207,13 +151,13 @@ export class DemoInMemoryAuthProvider implements OAuthServerProvider {
207151
}
208152

209153

210-
export const setupAuthServer = (authServerUrl: URL, config?: DemoOAuthProviderConfig): OAuthMetadata => {
154+
export const setupAuthServer = (authServerUrl: URL, mcpServerUrl: URL): OAuthMetadata => {
211155
// Create separate auth server app
212156
// NOTE: This is a separate app on a separate port to illustrate
213157
// how to separate an OAuth Authorization Server from a Resource
214158
// server in the SDK. The SDK is not intended to be provide a standalone
215159
// authorization server.
216-
const provider = new DemoInMemoryAuthProvider(config);
160+
const provider = new DemoInMemoryAuthProvider(mcpServerUrl);
217161
const authApp = express();
218162
authApp.use(express.json());
219163
// For introspection requests

src/server/auth/errors.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -189,13 +189,3 @@ export class InsufficientScopeError extends OAuthError {
189189
super("insufficient_scope", message, errorUri);
190190
}
191191
}
192-
193-
/**
194-
* Invalid target error - The requested resource is invalid, unknown, or malformed.
195-
* (RFC 8707 - Resource Indicators for OAuth 2.0)
196-
*/
197-
export class InvalidTargetError extends OAuthError {
198-
constructor(message: string, errorUri?: string) {
199-
super("invalid_target", message, errorUri);
200-
}
201-
}

src/server/auth/provider.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export type AuthorizationParams = {
88
scopes?: string[];
99
codeChallenge: string;
1010
redirectUri: string;
11-
resource?: string;
11+
resource?: URL;
1212
};
1313

1414
/**

src/server/auth/types.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ export interface AuthInfo {
2626
* The RFC 8707 resource server identifier for which this token is valid.
2727
* If set, this MUST match the MCP server's resource identifier (minus hash fragment).
2828
*/
29-
resource?: string;
29+
resource?: URL;
3030

3131
/**
3232
* Additional data associated with the token.

src/shared/auth-utils.ts

Lines changed: 5 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,33 +1,14 @@
11
/**
2-
* Utilities for handling OAuth resource URIs according to RFC 8707.
2+
* Utilities for handling OAuth resource URIs.
33
*/
44

55
/**
66
* Converts a server URL to a resource URL by removing the fragment.
77
* RFC 8707 section 2 states that resource URIs "MUST NOT include a fragment component".
88
* Keeps everything else unchanged (scheme, domain, port, path, query).
99
*/
10-
export function resourceUrlFromServerUrl(url: string): string {
11-
const hashIndex = url.indexOf('#');
12-
return hashIndex === -1 ? url : url.substring(0, hashIndex);
10+
export function resourceUrlFromServerUrl(url: URL): URL {
11+
const resourceURL = new URL(url.href);
12+
resourceURL.hash = ''; // Remove fragment
13+
return resourceURL;
1314
}
14-
15-
/**
16-
* Validates a resource URI according to RFC 8707 requirements.
17-
* @param resourceUri The resource URI to validate
18-
* @throws Error if the URI contains a fragment
19-
*/
20-
export function validateResourceUri(resourceUri: string): void {
21-
if (resourceUri.includes('#')) {
22-
throw new Error(`Invalid resource URI: ${resourceUri} - must not contain a fragment`);
23-
}
24-
}
25-
26-
/**
27-
* Extracts resource URI from server URL by removing fragment.
28-
* @param serverUrl The server URL to extract from
29-
* @returns The resource URI without fragment
30-
*/
31-
export function extractResourceUri(serverUrl: string | URL): string {
32-
return resourceUrlFromServerUrl(typeof serverUrl === 'string' ? serverUrl : serverUrl.href);
33-
}

0 commit comments

Comments
 (0)