https://github.com/tuannvm/oauth-mcp-proxy
OAuth 2.1 authentication library for Go MCP servers
https://github.com/tuannvm/oauth-mcp-proxy
authentication authorization claude codex go-sdk mcp mcp-go oauth
Last synced: 3 months ago
JSON representation
OAuth 2.1 authentication library for Go MCP servers
- Host: GitHub
- URL: https://github.com/tuannvm/oauth-mcp-proxy
- Owner: tuannvm
- License: mit
- Created: 2025-10-17T21:53:58.000Z (8 months ago)
- Default Branch: main
- Last Pushed: 2026-02-10T20:22:30.000Z (4 months ago)
- Last Synced: 2026-02-10T23:39:07.384Z (4 months ago)
- Topics: authentication, authorization, claude, codex, go-sdk, mcp, mcp-go, oauth
- Language: Go
- Homepage: https://docs.tuannvm.com/oauth-mcp-proxy
- Size: 278 KB
- Stars: 19
- Watchers: 1
- Forks: 6
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- License: LICENSE
- Security: docs/SECURITY.md
Awesome Lists containing this project
README
# OAuth MCP proxy
OAuth 2.1 authentication library for Go MCP servers.
**Supports both MCP SDKs:**
- ✅ `mark3labs/mcp-go`
- ✅ `modelcontextprotocol/go-sdk` (official)
**One-time setup:** Configure provider + add `WithOAuth()` to your server.
**Result:** All tools automatically protected with token validation and caching.
### mark3labs/mcp-go
```go
import "github.com/tuannvm/oauth-mcp-proxy/mark3labs"
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
Provider: "okta",
Issuer: "https://your-company.okta.com",
Audience: "api://your-mcp-server",
})
mcpServer := server.NewMCPServer("Server", "1.0.0", oauthOption)
streamable := server.NewStreamableHTTPServer(mcpServer, /*options*/)
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamable))
```
### Official SDK
```go
import mcpoauth "github.com/tuannvm/oauth-mcp-proxy/mcp"
mcpServer := mcp.NewServer(&mcp.Implementation{...}, nil)
_, handler, _ := mcpoauth.WithOAuth(mux, cfg, mcpServer)
http.ListenAndServe(":8080", handler)
```
[](https://github.com/tuannvm/oauth-mcp-proxy/actions/workflows/test.yml)
[](https://github.com/tuannvm/oauth-mcp-proxy/blob/main/go.mod)
[](https://goreportcard.com/report/github.com/tuannvm/oauth-mcp-proxy)
[](https://pkg.go.dev/github.com/tuannvm/oauth-mcp-proxy)
[](https://github.com/tuannvm/oauth-mcp-proxy/releases/latest)
[](https://opensource.org/licenses/MIT)
---
## Why Use This Library?
- **Dual SDK support** - Works with both mark3labs and official SDKs
- **Simple integration** - One `WithOAuth()` call protects all tools
- **Automatic 401 handling** - RFC 6750 compliant error responses with OAuth discovery
- **Zero per-tool config** - All tools automatically protected
- **Fast token caching** - 5-min cache with JWT expiry awareness
- **Security hardened** - State replay protection, DoS prevention, input validation
- **Built-in rate limiting** - Token-based rate limiter included
- **CORS support** - OPTIONS pass-through for browser clients
- **Multiple providers** - HMAC, Okta, Google, Azure AD
---
## How It Works
### Request Flow
```mermaid
sequenceDiagram
participant Client
participant MCP Server
box lightyellow oauth-mcp-proxy Library
participant Middleware
participant Cache
participant Provider
end
participant Your Tool Handler
Client->>MCP Server: Request + Bearer token
MCP Server->>Middleware: WithOAuth() intercepts
alt Token in cache and fresh
Middleware->>Cache: Check token hash
Cache-->>Middleware: Return cached user
else Token not cached or expired
Middleware->>Provider: Validate token (HMAC/OIDC)
Provider-->>Middleware: User claims
Middleware->>Cache: Store user for 5 minutes
end
Middleware->>Your Tool Handler: Pass request with user in context
Your Tool Handler->>Your Tool Handler: GetUserFromContext(ctx)
Your Tool Handler-->>Client: Send response
```
### Token Validation Flow
```mermaid
flowchart TB
Start([Your MCP Server receives request]) --> Extract[oauth-mcp-proxy: Extract Token]
Extract --> Hash[oauth-mcp-proxy: SHA-256 Hash]
Hash --> CheckCache{oauth-mcp-proxy: Token Cached?}
CheckCache -->|Cache Hit| GetUser[oauth-mcp-proxy: Get Cached User]
CheckCache -->|Cache Miss| Validate{oauth-mcp-proxy: Validate}
Validate -->|Valid| Claims[oauth-mcp-proxy: Extract Claims]
Validate -->|Invalid| Reject([Return 401])
Claims --> Store[oauth-mcp-proxy: Cache]
Store --> GetUser
GetUser --> Context[oauth-mcp-proxy: Add User to Context]
Context --> Tool[Your Tool Handler: GetUserFromContext]
Tool --> Response([Your MCP Server: Return Response])
style Start fill:#e8f5e9
style Extract fill:#fff9c4
style Hash fill:#fff9c4
style CheckCache fill:#fff9c4
style Validate fill:#fff9c4
style Claims fill:#fff9c4
style Store fill:#fff9c4
style GetUser fill:#fff9c4
style Context fill:#fff9c4
style Tool fill:#e8f5e9
style Response fill:#e8f5e9
style Reject fill:#ffebee
```
**What oauth-mcp-proxy does:**
1. Extracts Bearer tokens from HTTP requests
2. Validates against your OAuth provider (with caching)
3. Adds authenticated user to request context
4. All your tools automatically protected
---
## 🔒 Security Features
Production-ready security hardening built-in:
### State Replay Protection
- **Timestamp + nonce validation** - States include timestamp and nonce for replay attack prevention
- **Automatic nonce cleanup** - Expired nonces removed before replay check (prevents memory leaks)
- **Rolling deploy compatible** - Accepts legacy states without timestamp/nonce for zero-downtime upgrades
### Token Security
- **JWT expiry-aware caching** - Cache respects token expiration time (uses min(token.expiry, now+5min))
- **Constant-time HMAC comparison** - Timing attack prevention for signature verification
- **Secure nonce generation** - Panics on crypto/rand failure (no weak fallback)
### Input Validation & DoS Prevention
- **Parameter length limits** - code, state, code_challenge validated to prevent abuse
- **Request body size limits** - MaxBytesReader on token endpoint (1MB), registration (256KB)
- **Issuer URL validation** - Enforced HTTPS for non-localhost OIDC providers
### Session Management (Official SDK)
- **auth.TokenInfo population** - Populates go-sdk auth context for session binding
- **User-based session tracking** - Prevents session hijacking via user ID verification
### HTTP Security
- **Security headers** - CSP, X-Frame-Options, X-Content-Type-Options, Cache-Control
- **CORS support** - OPTIONS pass-through for browser clients
- **RFC 6750 compliant** - Proper WWW-Authenticate headers with resource_metadata
### Built-in Rate Limiting
```go
// Simple token-based rate limiter included
limiter := oauth.NewRateLimiter(time.Minute, 100)
if !limiter.Allow("client-ip") {
http.Error(w, "Rate limit exceeded", http.StatusTooManyRequests)
}
```
---
## Breaking Changes (Security Hardening)
### v1.0.0 → v1.1.0
The following security improvements introduce **breaking changes**:
**1. Issuer URL Validation (CRITICAL)**
- **Change**: OIDC providers now enforce HTTPS validation for issuer URLs
- **Impact**: Invalid issuer URLs will cause `NewServer()` to fail
- **Migration**: Ensure your `Issuer` config uses HTTPS (or localhost for testing)
```go
// ✅ Valid
Issuer: "https://company.okta.com"
Issuer: "http://localhost:8080" // Testing only
// ❌ Invalid - will fail validation
Issuer: "http://company.okta.com" // Not localhost
Issuer: "company.okta.com" // Missing scheme
```
**2. State Signing Key Initialization**
- **Change**: `NewServer()` now panics if state signing key cannot be generated
- **Impact**: Server startup will fail if crypto/rand fails (should never happen on healthy systems)
- **Migration**: Ensure your system has a working CSPRNG. No code changes needed.
**3. Nonce Generation Failure Behavior**
- **Change**: `generateSecureNonce()` now panics instead of falling back to weak timestamp-based nonces
- **Impact**: OAuth authorization requests will fail if crypto/rand fails
- **Migration**: Ensure your system has a working CSPRNG. No code changes needed.
**4. Error Message Simplification**
- **Change**: Security-sensitive error messages are less verbose to prevent information leakage
- **Impact**: Debugging authentication failures may require checking logs
- **Migration**: Use server logs for detailed debugging; client errors are intentionally generic
### No Migration Needed For
- **Token cache expiry fix** - Fully backwards compatible
- **State replay protection** - Legacy states without timestamp/nonce still accepted
- **Input validation** - Only affects malformed requests
- **go-sdk adapter fixes** - Fully backwards compatible
---
## Quick Start
### Using mark3labs/mcp-go
#### 1. Install
```bash
go get github.com/tuannvm/oauth-mcp-proxy
```
#### 2. Add to Your Server
```go
import (
oauth "github.com/tuannvm/oauth-mcp-proxy"
"github.com/tuannvm/oauth-mcp-proxy/mark3labs"
)
mux := http.NewServeMux()
// Enable OAuth (one time setup)
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
Provider: "okta", // or "hmac", "google", "azure"
Issuer: "https://your-company.okta.com",
Audience: "api://your-mcp-server",
ServerURL: "https://your-server.com",
})
// Create MCP server with OAuth
mcpServer := mcpserver.NewMCPServer("Server", "1.0.0", oauthOption)
// Add tools - all automatically protected
mcpServer.AddTool(myTool, myHandler)
// Setup endpoint with automatic 401 handling
streamable := mcpserver.NewStreamableHTTPServer(
mcpServer,
mcpserver.WithHTTPContextFunc(oauth.CreateHTTPContextFunc()),
)
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamable))
```
#### 3. Access Authenticated User
```go
func myHandler(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
user, ok := oauth.GetUserFromContext(ctx)
if !ok {
return nil, fmt.Errorf("authentication required")
}
// Use user.Username, user.Email, user.Subject
}
```
---
### Using Official SDK
#### 1. Install
```bash
go get github.com/modelcontextprotocol/go-sdk
go get github.com/tuannvm/oauth-mcp-proxy
```
#### 2. Add to Your Server
```go
import (
"github.com/modelcontextprotocol/go-sdk/mcp"
oauth "github.com/tuannvm/oauth-mcp-proxy"
mcpoauth "github.com/tuannvm/oauth-mcp-proxy/mcp"
)
mux := http.NewServeMux()
// Create MCP server
mcpServer := mcp.NewServer(&mcp.Implementation{
Name: "my-server",
Version: "1.0.0",
}, nil)
// Add tools
mcp.AddTool(mcpServer, &mcp.Tool{
Name: "greet",
Description: "Greet user",
}, func(ctx context.Context, req *mcp.CallToolRequest, params *struct{}) (*mcp.CallToolResult, any, error) {
user, _ := oauth.GetUserFromContext(ctx)
return &mcp.CallToolResult{
Content: []mcp.Content{
&mcp.TextContent{Text: "Hello, " + user.Username},
},
}, nil, nil
})
// Add OAuth protection
_, handler, _ := mcpoauth.WithOAuth(mux, &oauth.Config{
Provider: "okta",
Issuer: "https://your-company.okta.com",
Audience: "api://your-mcp-server",
}, mcpServer)
http.ListenAndServe(":8080", handler)
```
Your MCP server now requires OAuth authentication.
---
## Examples
See [examples/README.md](examples/README.md) for detailed setup guide including Okta configuration.
| SDK | Example | Description |
|-----|---------|-------------|
| **mark3labs** | [Simple](examples/mark3labs/simple/) | Minimal setup - copy/paste ready |
| **mark3labs** | [Advanced](examples/mark3labs/advanced/) | ConfigBuilder, multiple tools, logging |
| **Official** | [Simple](examples/official/simple/) | Minimal setup - copy/paste ready |
| **Official** | [Advanced](examples/official/advanced/) | ConfigBuilder, multiple tools, logging |
---
## Supported Providers
| Provider | Best For | Setup Guide |
|----------|----------|-------------|
| **HMAC** | Testing, development | [docs/providers/HMAC.md](docs/providers/HMAC.md) |
| **Okta** | Enterprise SSO | [docs/providers/OKTA.md](docs/providers/OKTA.md) |
| **Google** | Google Workspace | [docs/providers/GOOGLE.md](docs/providers/GOOGLE.md) |
| **Azure AD** | Microsoft 365 | [docs/providers/AZURE.md](docs/providers/AZURE.md) |
---
## Documentation
**Getting Started:**
- [Setup Guide](docs/CLIENT-SETUP.md) - Complete server integration and client configuration
- [Configuration Guide](docs/CONFIGURATION.md) - All config options
- [Provider Setup](docs/providers/) - OAuth provider guides
**Advanced:**
- [Security Guide](docs/SECURITY.md) - Production best practices
- [Troubleshooting](docs/TROUBLESHOOTING.md) - Common issues
---
## License
MIT License - See [LICENSE](LICENSE)