Skip to content

Feat/setup mcp auth - #681

Open
akulistus wants to merge 7 commits into
feat/setup-mcpfrom
feat/setup-mcp-auth
Open

akulistus wants to merge 7 commits into
feat/setup-mcpfrom
feat/setup-mcp-auth

Conversation

@akulistus

@akulistus akulistus commented Sep 16, 2026 •

Copy link
Copy Markdown

You can use modified frontend version to test mcp and authorization flow

@akulistus
akulistus added this pull request to stack #682 September 16, 2026 00:43
Comment thread src/integrations/mcp/auth.ts Outdated
`${process.env.API_URL}/.well-known/oauth-protected-resource/integration/mcp`
});

export const useMCPAuth = (app: express.Application) => {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

use named functions instead of anonymous. It's better for debugging.

Suggested change
export const useMCPAuth = (app: express.Application) => {
export function useMCPAuth(app: express.Application) {

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated

export const useMCPAuth = (app: express.Application) => {
/**
* Dynamic client registration

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, provide a little more descriptive docs.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +83 to +84
verifier: tokenVerifier,
resourceMetadataUrl:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
});

/**
* Protected resource metadata

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same here. Explain why this method is needed and what is does.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
);

/**
* Frontend callback

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

improve docs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
expiresAt: number;
};

const authCodes = new Map<string, AuthCodeData>();

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this maps will be shared across all users, is it ok? Also, add docs please

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Implemented redis store for multi-instance deployment and improved in-memory store for single-instance deployment

Yes. It's fine, because the storage maps user's unique auth code to authRequest data. The user should not be able to access any other authRequset data except for it's own

Comment thread src/integrations/mcp/auth.ts Outdated
});

/**
* MCP client callback

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bad docs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
return payload as TokenData;
};

const createTokenResponse = (userId: string, clientId: string) => ({

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

add docs

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +47 to +61
const createTokenResponse = (userId: string, clientId: string) => ({
access_token: jwt.sign(
{ userId, clientId, tokenUse: "access" },
process.env.JWT_SECRET_ACCESS_TOKEN as Secret,
{ expiresIn: accessTokenLifetimeSeconds }
),
refresh_token: jwt.sign(
{ userId, clientId, tokenUse: "refresh" },
process.env.JWT_SECRET_ACCESS_TOKEN as Secret,
{ expiresIn: "30d" }
),
token_type: "Bearer",
expires_in: accessTokenLifetimeSeconds,
scope: "mcp:tools mcp:resources"
});

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

can we reuse generateTokensPair method here?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Token isolation, redirect validation, CORS, and distributed authorization-code storage must be addressed before approval.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Adds OAuth authorization and bearer-token protection to the MCP integration.

Changes:

  • Adds OAuth discovery, registration, consent, token, PKCE, and refresh flows.
  • Protects MCP routes and adds an authenticated user-ID tool.
  • Adds the MCP Express dependency.
File summaries
File Description
src/integrations/mcp/auth.ts Implements MCP OAuth flow.
src/integrations/mcp/index.ts Applies authentication middleware.
src/integrations/mcp/mcp.ts Adds an authenticated test tool.
package.json Adds MCP Express dependency.
yarn.lock Locks the new dependency.
Review details
  • Files reviewed: 4/5 changed files
  • Comments generated: 9
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +53 to +55
refresh_token: jwt.sign(
{ userId, clientId, tokenUse: "refresh" },
process.env.JWT_SECRET_ACCESS_TOKEN as Secret,
Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +188 to +192
authCodes.set(code, {
userId: user.userId,
clientId: client_id,
redirectUri: redirect_uri,
codeChallenge: code_challenge,
Comment thread src/integrations/mcp/auth.ts Outdated
expiresAt: number;
};

const authCodes = new Map<string, AuthCodeData>();
Comment thread src/integrations/mcp/auth.ts Outdated
{ expiresIn: "30d" }
),
token_type: "Bearer",
expires_in: accessTokenLifetimeSeconds,
Comment thread src/integrations/mcp/auth.ts Outdated
/**
* Frontend callback
*/
app.post("/concent/integration/mcp", (req, res) => {
Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +179 to +182
const user = jwt.verify(
loginToken,
process.env.JWT_SECRET_ACCESS_TOKEN as Secret
) as UserJWTData;
Comment thread src/integrations/mcp/auth.ts Outdated
Comment on lines +291 to +294
const calculatedChallenge = crypto
.createHash("sha256")
.update(code_verifier)
.digest("base64url");
Comment thread src/integrations/mcp/auth.ts Outdated
`${process.env.API_URL}/.well-known/oauth-protected-resource/integration/mcp`
});

export const useMCPAuth = (app: express.Application) => {
Comment thread src/integrations/mcp/mcp.ts Outdated
server.registerTool(
"print_userId",
{
description: "A test tool that pritn userId from auth token"
Comment thread src/integrations/mcp/index.ts Outdated
/**
* MCP
*/
app.use("/integration/mcp", authMiddleware, router);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Under WHATWG Fetch §3.2.1, browser CORS preflights (OPTIONS) do not carry credentials (Authorization). Because OPTIONS currently cascades into authMiddleware (requireBearerAuth), it responds with 401 Unauthorized per RFC 6750 §3.1, causing browsers to abort cross-origin MCP requests.

Consider either:

  • intercepting OPTIONS before authMiddleware to respond with 204 to keep changes local to this route
  • terminating OPTIONS preflights with 204 and adding Authorization to Access-Control-Allow-Headers

For discovery endpoints (/.well-known/oauth-*), RFC 8414 §3 also recommends Access-Control-Allow-Origin: * for external clients.

Comment thread src/integrations/mcp/auth.ts Outdated
});
}

const auth = authCodes.get(code);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Per RFC 6749 §10.5 and §4.1.2, authorization codes should be single-use. Currently authCodes.delete(code) is only reached after PKCE verification succeeds, so on parameter or PKCE failure the code remains active for 5 minutes.

Consider consuming/invalidating the code upon initial lookup (or ensuring deletion in all error branches) to prevent replay or brute-force verifier probing against an intercepted code.

Comment thread src/integrations/mcp/mcp.ts Outdated
description: "A test tool that pritn userId from auth token"
},
async (ctx: ServerContext) => {
const token = ctx.http?.req?.headers.get("authorization")?.slice(7)!;

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Accessing ctx.http?.req?.headers.get("authorization")?.slice(7)! directly and passing it to jwt.decode can throw an unhandled TypeError if the header is missing, malformed, or formatted differently, and reads unverified claims instead of the token already verified by requireBearerAuth.

@akulistus
akulistus force-pushed the feat/setup-mcp-auth branch from 4a261e7 to c83154d Compare October 14, 2026 15:40
@akulistus
akulistus force-pushed the feat/setup-mcp-auth branch 2 times, most recently from 6fe3738 to 7401a65 Compare October 22, 2026 20:16
@akulistus
akulistus force-pushed the feat/setup-mcp-auth branch from 7401a65 to 08d5031 Compare October 23, 2026 21:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants