Skip to content

Commit c21063c

Browse files
committed
feat: implement optional OAuth 2.1 authentication with modular middleware and configuration
1 parent 9ca6440 commit c21063c

File tree

9 files changed

+370
-3
lines changed

9 files changed

+370
-3
lines changed

.cursorrules

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,11 @@ This is a TypeScript template for building Model Context Protocol (MCP) servers.
3636
- Defines all available MCP tools with their JSON schemas
3737
- Routes tool calls to registered tool handlers
3838
- Handles error responses in MCP format
39+
- Conditionally applies OAuth middleware based on configuration
3940
- **`src/config.ts`** - Environment configuration with validation using Zod
4041
- **`src/logger.ts`** - Structured logging with Pino (OpenTelemetry compatible)
4142
- **`src/lib/utils.ts`** - Utility functions for MCP response formatting
43+
- **`src/auth/`** - Optional OAuth 2.1 authentication module (can be completely removed)
4244

4345
### Template MCP Tools Available
4446

@@ -101,6 +103,16 @@ The following environment variables are supported (see `src/config.ts`):
101103
- `SERVER_VERSION` - Server version (default: 1.0.0)
102104
- `LOG_LEVEL` - Logging level (error/warn/info/debug, default: info)
103105

106+
### OAuth Configuration (Optional)
107+
108+
- `ENABLE_AUTH` - Enable OAuth authentication (default: false)
109+
- `OAUTH_CLIENT_ID` - OAuth client ID (required if auth enabled)
110+
- `OAUTH_CLIENT_SECRET` - OAuth client secret (required if auth enabled)
111+
- `OAUTH_AUTH_ENDPOINT` - OAuth authorization endpoint (required if auth enabled)
112+
- `OAUTH_TOKEN_ENDPOINT` - OAuth token endpoint (required if auth enabled)
113+
- `OAUTH_SCOPE` - OAuth scope (default: "read")
114+
- `OAUTH_REDIRECT_URI` - OAuth redirect URI (required if auth enabled)
115+
104116
## Coding Guidelines
105117

106118
- Follow existing patterns in the codebase
@@ -119,4 +131,30 @@ The following environment variables are supported (see `src/config.ts`):
119131
- Include relevant context in log messages (user IDs, session IDs, etc.)
120132
- Log structured data as the second parameter: `logger.info("message", { key: value })`
121133
- Error logs should include error details: `logger.error("Error message", { error: error.message })`
122-
- The logger automatically includes trace correlation when OpenTelemetry is configured
134+
- The logger automatically includes trace correlation when OpenTelemetry is configured
135+
136+
## OAuth Implementation
137+
138+
### Modular Authentication
139+
140+
The template includes optional OAuth 2.1 authentication that can be easily enabled or completely removed:
141+
142+
- **Modular Design**: All OAuth code is in `src/auth/` directory
143+
- **Conditional Loading**: OAuth middleware only applies when `ENABLE_AUTH=true`
144+
- **Zero Impact When Disabled**: No performance overhead when authentication is disabled
145+
- **Easy Removal**: Delete `src/auth/` directory and remove auth import from `src/index.ts`
146+
147+
### Authentication Patterns
148+
149+
1. **External OAuth (Recommended)**: Use Pomerium or similar OAuth proxy
150+
2. **Built-in OAuth Server**: Use the provided OAuth implementation in `src/auth/`
151+
152+
### Removing OAuth
153+
154+
To completely remove OAuth support:
155+
156+
1. Delete the `src/auth/` directory
157+
2. Remove the auth import and middleware lines from `src/index.ts`
158+
3. Remove OAuth environment variables from `src/config.ts`
159+
160+
The core MCP server functionality is completely independent of the authentication layer.

.env.example

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# Server Configuration
2+
PORT=3000
3+
NODE_ENV=development
4+
SERVER_NAME=mcp-typescript-template
5+
SERVER_VERSION=1.0.0
6+
LOG_LEVEL=info
7+
8+
# OAuth Configuration (Optional)
9+
# Set ENABLE_AUTH=true to enable OAuth authentication
10+
ENABLE_AUTH=false
11+
12+
# Required if ENABLE_AUTH=true
13+
OAUTH_CLIENT_ID=your-client-id
14+
OAUTH_CLIENT_SECRET=your-client-secret
15+
OAUTH_AUTH_ENDPOINT=https://auth.example.com/oauth/authorize
16+
OAUTH_TOKEN_ENDPOINT=https://auth.example.com/oauth/token
17+
OAUTH_SCOPE=read
18+
OAUTH_REDIRECT_URI=http://localhost:3000/oauth/callback

CLAUDE.md

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ This is a TypeScript template for building Model Context Protocol (MCP) servers.
3232
- Defines all available MCP tools with their JSON schemas
3333
- Routes tool calls to registered tool handlers
3434
- Handles error responses in MCP format
35+
- Conditionally applies OAuth middleware based on configuration
3536
- **`src/config.ts`** - Environment configuration with validation using Zod
3637
- **`src/logger.ts`** - Structured logging with Pino (OpenTelemetry compatible)
3738
- **`src/lib/utils.ts`** - Utility functions for MCP response formatting
39+
- **`src/auth/`** - Optional OAuth 2.1 authentication module (can be completely removed)
3840

3941
### Template MCP Tools Available
4042

@@ -89,6 +91,16 @@ The following environment variables are supported (see `src/config.ts`):
8991
- `SERVER_VERSION` - Server version (default: 1.0.0)
9092
- `LOG_LEVEL` - Logging level (error/warn/info/debug, default: info)
9193

94+
### OAuth Configuration (Optional)
95+
96+
- `ENABLE_AUTH` - Enable OAuth authentication (default: false)
97+
- `OAUTH_CLIENT_ID` - OAuth client ID (required if auth enabled)
98+
- `OAUTH_CLIENT_SECRET` - OAuth client secret (required if auth enabled)
99+
- `OAUTH_AUTH_ENDPOINT` - OAuth authorization endpoint (required if auth enabled)
100+
- `OAUTH_TOKEN_ENDPOINT` - OAuth token endpoint (required if auth enabled)
101+
- `OAUTH_SCOPE` - OAuth scope (default: "read")
102+
- `OAUTH_REDIRECT_URI` - OAuth redirect URI (required if auth enabled)
103+
92104
## Logging Best Practices
93105

94106
- Use appropriate log levels: `error`, `warn`, `info`, `debug`
@@ -108,4 +120,30 @@ When adding new tools to the MCP server:
108120
4. Return responses in MCP content format with JSON stringified data
109121
5. Handle errors gracefully and return appropriate error messages
110122
6. Use structured logging to track tool usage: `logger.info("Tool executed", { toolName, args })`
111-
7. Log errors with context: `logger.error("Tool execution failed", { toolName, error: error.message })`
123+
7. Log errors with context: `logger.error("Tool execution failed", { toolName, error: error.message })`
124+
125+
## OAuth Implementation
126+
127+
### Modular Authentication
128+
129+
The template includes optional OAuth 2.1 authentication that can be easily enabled or completely removed:
130+
131+
- **Modular Design**: All OAuth code is in `src/auth/` directory
132+
- **Conditional Loading**: OAuth middleware only applies when `ENABLE_AUTH=true`
133+
- **Zero Impact When Disabled**: No performance overhead when authentication is disabled
134+
- **Easy Removal**: Delete `src/auth/` directory and remove auth import from `src/index.ts`
135+
136+
### Authentication Patterns
137+
138+
1. **External OAuth (Recommended)**: Use Pomerium or similar OAuth proxy
139+
2. **Built-in OAuth Server**: Use the provided OAuth implementation in `src/auth/`
140+
141+
### Removing OAuth
142+
143+
To completely remove OAuth support:
144+
145+
1. Delete the `src/auth/` directory
146+
2. Remove the auth import and middleware lines from `src/index.ts`
147+
3. Remove OAuth environment variables from `src/config.ts`
148+
149+
The core MCP server functionality is completely independent of the authentication layer.

README.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ This template provides:
1212
- **ESLint + Prettier** - Code quality and formatting
1313
- **Docker** - Containerization support
1414
- **Example Tool** - Simple echo tool to demonstrate MCP tool implementation
15+
- **OAuth 2.1 Compatible** - Optional OAuth implementation (can use Pomerium for external auth or built-in server implementation)
1516

1617
## Getting Started
1718

@@ -184,6 +185,21 @@ server.registerTool(
184185
);
185186
```
186187

188+
## Authentication & Authorization
189+
190+
### OAuth 2.1 Support
191+
192+
This template supports **optional** OAuth 2.1 authentication with two approaches:
193+
194+
1. **External OAuth (Recommended)** - Use Pomerium or similar OAuth proxy to handle authentication externally
195+
2. **Built-in OAuth Server** - Implement OAuth directly in the MCP server using the provided modular implementation
196+
197+
The OAuth implementation is designed to be easily added or removed without affecting core server functionality.
198+
199+
### Enabling OAuth
200+
201+
To enable OAuth authentication, see the `src/auth/` directory for a modular OAuth implementation that can be toggled via environment variables.
202+
187203
## Why Express?
188204

189205
This template uses Express for the HTTP server, which provides:

src/auth/index.ts

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import { OAuthProvider, OAuthConfig } from "./oauth-provider.js";
2+
import { createOAuthMiddleware, createOptionalOAuthMiddleware } from "./middleware.js";
3+
import { getConfig } from "../config.js";
4+
5+
export { OAuthProvider, type OAuthConfig, type AuthenticatedRequest } from "./oauth-provider.js";
6+
export { createOAuthMiddleware, createOptionalOAuthMiddleware } from "./middleware.js";
7+
8+
/**
9+
* Initialize OAuth provider if authentication is enabled
10+
*/
11+
export function initializeAuth() {
12+
const config = getConfig();
13+
14+
if (!config.ENABLE_AUTH) {
15+
return null;
16+
}
17+
18+
const oauthConfig: OAuthConfig = {
19+
clientId: config.OAUTH_CLIENT_ID!,
20+
clientSecret: config.OAUTH_CLIENT_SECRET!,
21+
authorizationEndpoint: config.OAUTH_AUTH_ENDPOINT!,
22+
tokenEndpoint: config.OAUTH_TOKEN_ENDPOINT!,
23+
scope: config.OAUTH_SCOPE || "read",
24+
redirectUri: config.OAUTH_REDIRECT_URI!,
25+
};
26+
27+
return new OAuthProvider(oauthConfig);
28+
}
29+
30+
/**
31+
* Create authentication middleware based on configuration
32+
*/
33+
export function createAuthMiddleware() {
34+
const oauthProvider = initializeAuth();
35+
36+
if (!oauthProvider) {
37+
// Return pass-through middleware when auth is disabled
38+
return (_req: any, _res: any, next: any) => next();
39+
}
40+
41+
return createOAuthMiddleware(oauthProvider);
42+
}

src/auth/middleware.ts

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
import { Request, Response, NextFunction } from "express";
2+
import { OAuthProvider } from "./oauth-provider.js";
3+
import { logger } from "../logger.js";
4+
5+
export interface AuthenticatedRequest extends Request {
6+
userId?: string;
7+
accessToken?: string;
8+
}
9+
10+
/**
11+
* Create OAuth middleware that can be easily added/removed
12+
*/
13+
export function createOAuthMiddleware(oauthProvider: OAuthProvider) {
14+
return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
15+
const authHeader = req.headers.authorization;
16+
17+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
18+
return res.status(401).json({
19+
error: "unauthorized",
20+
error_description: "Missing or invalid authorization header",
21+
});
22+
}
23+
24+
const token = authHeader.substring(7); // Remove "Bearer " prefix
25+
26+
try {
27+
const validation = await oauthProvider.validateToken(token);
28+
29+
if (!validation.valid) {
30+
return res.status(401).json({
31+
error: "invalid_token",
32+
error_description: "The access token is invalid or expired",
33+
});
34+
}
35+
36+
// Add user context to request
37+
req.userId = validation.userId;
38+
req.accessToken = token;
39+
40+
logger.info("Request authenticated", { userId: validation.userId });
41+
next();
42+
} catch (error) {
43+
logger.error("Authentication middleware error", { error: error.message });
44+
return res.status(500).json({
45+
error: "server_error",
46+
error_description: "Internal server error during authentication",
47+
});
48+
}
49+
};
50+
}
51+
52+
/**
53+
* Optional middleware that only authenticates if token is present
54+
*/
55+
export function createOptionalOAuthMiddleware(oauthProvider: OAuthProvider) {
56+
return async (req: AuthenticatedRequest, res: Response, next: NextFunction) => {
57+
const authHeader = req.headers.authorization;
58+
59+
if (!authHeader || !authHeader.startsWith("Bearer ")) {
60+
// No auth header, continue without authentication
61+
return next();
62+
}
63+
64+
const token = authHeader.substring(7);
65+
66+
try {
67+
const validation = await oauthProvider.validateToken(token);
68+
69+
if (validation.valid) {
70+
req.userId = validation.userId;
71+
req.accessToken = token;
72+
logger.info("Request authenticated", { userId: validation.userId });
73+
}
74+
75+
next();
76+
} catch (error) {
77+
logger.warn("Optional authentication failed", { error: error.message });
78+
next(); // Continue without authentication
79+
}
80+
};
81+
}

0 commit comments

Comments
 (0)