Skip to content

Commit d2d1c67

Browse files
committed
refactor(auth): use URL constructor for endpoint generation in OAuth handlers
1 parent 64fd0e4 commit d2d1c67

File tree

5 files changed

+34
-30
lines changed

5 files changed

+34
-30
lines changed

src/auth/discovery.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ export function createAuthorizationServerMetadataHandler() {
1717

1818
const metadata = {
1919
issuer: baseUrl,
20-
authorization_endpoint: `${baseUrl}/oauth/authorize`,
21-
token_endpoint: `${baseUrl}/oauth/token`,
20+
authorization_endpoint: new URL("/oauth/authorize", baseUrl).toString(),
21+
token_endpoint: new URL("/oauth/token", baseUrl).toString(),
2222
response_types_supported: ["code"],
2323
grant_types_supported: ["authorization_code"],
2424
code_challenge_methods_supported: ["S256"],
@@ -60,7 +60,7 @@ export function createProtectedResourceMetadataHandler() {
6060
authorization_servers: [baseUrl],
6161
scopes_supported: ["read", "write", "mcp"],
6262
bearer_methods_supported: ["header"],
63-
resource_documentation: `${baseUrl}/docs`
63+
resource_documentation: new URL("/docs", baseUrl).toString()
6464
};
6565

6666
logger.info("OAuth protected resource metadata requested", {

src/auth/routes.ts

Lines changed: 19 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ const pendingRequests = new Map<string, PendingAuthRequest>();
3636
export function createAuthorizeHandler() {
3737
return async (req: Request, res: Response) => {
3838
try {
39+
logger.debug("Authorization handler called", {
40+
query: req.query,
41+
url: req.url
42+
});
43+
3944
const config = getConfig();
4045
const {
4146
response_type,
@@ -94,28 +99,25 @@ export function createAuthorizeHandler() {
9499
});
95100

96101
// Build authorization URL for external provider with our own PKCE
97-
const authParams = new URLSearchParams({
98-
response_type: "code",
99-
client_id: config.OAUTH_CLIENT_ID!,
100-
redirect_uri: config.OAUTH_REDIRECT_URI!,
101-
scope: scope as string || "openid profile email",
102-
state: requestId, // Use our request ID as state
103-
code_challenge: externalCodeChallenge, // Use our generated challenge
104-
code_challenge_method: "S256"
105-
});
106-
107-
const authUrl = `${config.OAUTH_ISSUER}/oauth/authorize?${authParams}`;
102+
const authUrl = new URL("/oauth/authorize", config.OAUTH_ISSUER!);
103+
authUrl.searchParams.set("response_type", "code");
104+
authUrl.searchParams.set("client_id", config.OAUTH_CLIENT_ID!);
105+
authUrl.searchParams.set("redirect_uri", config.OAUTH_REDIRECT_URI!);
106+
authUrl.searchParams.set("scope", scope as string || "openid profile email");
107+
authUrl.searchParams.set("state", requestId);
108+
authUrl.searchParams.set("code_challenge", externalCodeChallenge);
109+
authUrl.searchParams.set("code_challenge_method", "S256");
108110

109111
logger.info("Proxying OAuth authorization request", {
110112
client_id,
111113
redirect_uri,
112114
scope,
113115
requestId,
114-
external_auth_url: `${config.OAUTH_ISSUER}/oauth/authorize`
116+
external_auth_url: new URL("/oauth/authorize", config.OAUTH_ISSUER!).toString()
115117
});
116118

117119
// Redirect to external OAuth provider
118-
res.redirect(authUrl);
120+
res.redirect(authUrl.toString());
119121

120122
} catch (error) {
121123
logger.error("OAuth authorization proxy error", {
@@ -279,7 +281,7 @@ export function createCallbackHandler(oauthProvider: OAuthProvider) {
279281
*/
280282
async function exchangeCodeForTokens(code: string, config: any, codeVerifier: string): Promise<TokenExchangeResponse | null> {
281283
try {
282-
const tokenEndpoint = `${config.OAUTH_ISSUER}/oauth/token`;
284+
const tokenEndpoint = new URL("/oauth/token", config.OAUTH_ISSUER!);
283285

284286
const tokenParams = new URLSearchParams({
285287
grant_type: "authorization_code",
@@ -291,11 +293,11 @@ async function exchangeCodeForTokens(code: string, config: any, codeVerifier: st
291293
});
292294

293295
logger.info("Exchanging authorization code with external provider", {
294-
tokenEndpoint,
296+
tokenEndpoint: tokenEndpoint.toString(),
295297
clientId: config.OAUTH_CLIENT_ID
296298
});
297299

298-
const response = await fetch(tokenEndpoint, {
300+
const response = await fetch(tokenEndpoint.toString(), {
299301
method: "POST",
300302
headers: {
301303
"Content-Type": "application/x-www-form-urlencoded",
@@ -310,7 +312,7 @@ async function exchangeCodeForTokens(code: string, config: any, codeVerifier: st
310312
status: response.status,
311313
statusText: response.statusText,
312314
error: errorText,
313-
tokenEndpoint,
315+
tokenEndpoint: tokenEndpoint.toString(),
314316
clientId: config.OAUTH_CLIENT_ID
315317
});
316318
return null;

src/auth/token-validator.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ export class OAuthTokenValidator {
4040
private async validateJWT(token: string): Promise<TokenValidationResult> {
4141
try {
4242
// Get JWKS from the issuer
43-
const JWKS = jose.createRemoteJWKSet(new URL(`${this.#issuer}/.well-known/jwks.json`));
43+
const JWKS = jose.createRemoteJWKSet(new URL("/.well-known/jwks.json", this.#issuer));
4444

4545
// Verify and decode the JWT
4646
const verifyOptions: any = {
@@ -78,9 +78,9 @@ export class OAuthTokenValidator {
7878
}
7979

8080
private async introspectToken(token: string): Promise<TokenValidationResult> {
81-
const introspectionUrl = `${this.#issuer}/oauth/introspect`;
81+
const introspectionUrl = new URL("/oauth/introspect", this.#issuer);
8282

83-
const response = await fetch(introspectionUrl, {
83+
const response = await fetch(introspectionUrl.toString(), {
8484
method: "POST",
8585
headers: {
8686
"Content-Type": "application/x-www-form-urlencoded",

src/config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,8 @@ export function getConfig(): Config {
4040
// Provide default for OAUTH_REDIRECT_URI if not set
4141
if (!parsed.OAUTH_REDIRECT_URI) {
4242
const baseUrl = parsed.BASE_URL || "http://localhost:3000";
43-
parsed.OAUTH_REDIRECT_URI = `${baseUrl}/callback`;
43+
const callbackUrl = new URL("/callback", baseUrl);
44+
parsed.OAUTH_REDIRECT_URI = callbackUrl.toString();
4445
console.log(`⚠️ OAUTH_REDIRECT_URI not set, using default: ${parsed.OAUTH_REDIRECT_URI}`);
4546
}
4647

src/index.ts

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -113,10 +113,10 @@ const mcpHandler = async (req: express.Request, res: express.Response) => {
113113
capabilities,
114114
...(config.AUTH_MODE !== "none" && {
115115
oauth: {
116-
authorization_server: `${config.BASE_URL || "http://localhost:3000"}/.well-known/oauth-authorization-server`,
117-
protected_resource: `${config.BASE_URL || "http://localhost:3000"}/.well-known/oauth-protected-resource`,
118-
authorization_endpoint: `${config.BASE_URL || "http://localhost:3000"}/oauth/authorize`,
119-
token_endpoint: `${config.BASE_URL || "http://localhost:3000"}/oauth/token`
116+
authorization_server: new URL("/.well-known/oauth-authorization-server", config.BASE_URL || "http://localhost:3000").toString(),
117+
protected_resource: new URL("/.well-known/oauth-protected-resource", config.BASE_URL || "http://localhost:3000").toString(),
118+
authorization_endpoint: new URL("/oauth/authorize", config.BASE_URL || "http://localhost:3000").toString(),
119+
token_endpoint: new URL("/oauth/token", config.BASE_URL || "http://localhost:3000").toString()
120120
}
121121
})
122122
});
@@ -133,11 +133,12 @@ const config = getConfig();
133133
let oauthProvider: OAuthProvider | null = null;
134134

135135
if (config.AUTH_MODE === "full") {
136+
const baseUrl = config.BASE_URL || "http://localhost:3000";
136137
oauthProvider = new OAuthProvider({
137138
clientId: "mcp-client",
138139
clientSecret: "mcp-secret",
139-
authorizationEndpoint: `${config.BASE_URL || "http://localhost:3000"}/oauth/authorize`,
140-
tokenEndpoint: `${config.BASE_URL || "http://localhost:3000"}/oauth/token`,
140+
authorizationEndpoint: new URL("/oauth/authorize", baseUrl).toString(),
141+
tokenEndpoint: new URL("/oauth/token", baseUrl).toString(),
141142
scope: config.OAUTH_SCOPE,
142143
redirectUri: config.OAUTH_REDIRECT_URI!
143144
});

0 commit comments

Comments
 (0)