Skip to content

Commit 14f9009

Browse files
committed
feat(tutorials): Add detailed OAuth server setup and 401 handling guide
Signed-off-by: Tommy Nguyen <tuannvm@hotmail.com>
1 parent d71ecb7 commit 14f9009

File tree

4 files changed

+796
-75
lines changed

4 files changed

+796
-75
lines changed

CLAUDE.md

Lines changed: 102 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -134,15 +134,76 @@ Run single test:
134134
go test -v -run TestName ./...
135135
```
136136

137+
### Test Patterns
138+
139+
Tests use **table-driven subtests** with `t.Run()`:
140+
141+
```go
142+
tests := []struct {
143+
name string
144+
// test fields
145+
}{...}
146+
for _, tt := range tests {
147+
t.Run(tt.name, func(t *testing.T) {
148+
// test body
149+
})
150+
}
151+
```
152+
153+
Mock validators implement `TokenValidator` interface. Use `httptest.NewRecorder()` for HTTP handler tests.
154+
155+
## Configuration
156+
157+
### ConfigBuilder Pattern (Recommended)
158+
159+
Use `ConfigBuilder` for production code instead of direct `Config` structs:
160+
161+
```go
162+
cfg, _ := oauth.NewConfigBuilder().
163+
WithProvider("okta").
164+
WithIssuer("https://company.okta.com").
165+
WithAudience("api://my-server").
166+
WithHost(host).WithPort(port).
167+
Build()
168+
```
169+
170+
`Build()` validates config and auto-constructs `ServerURL` if not set.
171+
172+
### Context Timeouts
173+
174+
- **OIDC validation**: 10 seconds
175+
- **Provider initialization**: 30 seconds
176+
177+
## Security Requirements
178+
179+
1. **Redirect URI validation**: All URIs must be in explicit allowlist
180+
2. **State parameter HMAC**: OAuth states are HMAC-signed to prevent CSRF
181+
3. **Audience validation**: Both HMAC and OIDC validators explicitly check `aud` claim
182+
4. **No raw token logging**: Only log `fmt.Sprintf("%x", sha256.Sum256([]byte(token)))[:16]`
183+
5. **TLS in production**: Always warn if `useTLS=false` in `LogStartup()`
137184

138185
## Important Notes
139186

140187
1. **User Context**: Always use `GetUserFromContext(ctx)` in tool handlers to access authenticated user
141-
2. **Token Caching**: Tokens cached for 5 minutes - design for this TTL in testing
188+
2. **Token Caching**: Tokens cached for 5 minutes - design for this TTL in testing. Cache uses `sync.RWMutex` with background cleanup via `deleteExpiredToken()` goroutine
142189
3. **Logging**: Config.Logger is optional. If nil, uses default logger (log.Printf with level prefixes)
143-
4. **Modes**: Library supports "native" (token validation only) and "proxy" (OAuth flow proxy) modes
144-
5. **Security**: All redirect URIs validated, state parameters HMAC-signed, tokens never logged (only hash previews)
145-
6. **Adapter Pattern**: `WithOAuth()` is in adapter packages (`mark3labs.WithOAuth()` or `mcp.WithOAuth()`) for SDK-specific integration.
190+
4. **Modes**: Library supports "native" (token validation only) and "proxy" (OAuth flow proxy) modes. Auto-detected based on `ClientID` presence
191+
5. **Adapter Pattern**: `WithOAuth()` is in adapter packages (`mark3labs.WithOAuth()` or `mcp.WithOAuth()`) for SDK-specific integration
192+
193+
## Common Gotchas
194+
195+
1. **SDK Imports**: Adapter code (`mark3labs/`, `mcp/`) can import SDKs. **Core package cannot** - keep it SDK-agnostic
196+
2. **Context Propagation**: Always extract user via `GetUserFromContext(ctx)` in tool handlers
197+
3. **Cache Expiry**: Background cleanup runs in goroutine to avoid lock contention
198+
4. **Mode Detection**: Config auto-detects "native" vs "proxy" based on `ClientID` presence
199+
5. **Logger Fallback**: If `cfg.Logger == nil`, uses `defaultLogger{}` with `log.Printf`
200+
201+
## File Naming Conventions
202+
203+
- Core logic: `oauth.go`, `config.go`, `cache.go`, `context.go`, `handlers.go`, `middleware.go`
204+
- Tests: `*_test.go` (e.g., `security_test.go`, `integration_test.go`)
205+
- Adapters: `mark3labs/oauth.go`, `mcp/oauth.go` (not `*_adapter.go`)
206+
- Provider: `provider/provider.go` (single file, multiple validators)
146207

147208
## Using the Library
148209

@@ -153,10 +214,15 @@ import (
153214
"github.com/tuannvm/oauth-mcp-proxy/mark3labs"
154215
)
155216

156-
_, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{...})
217+
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{...})
157218
mcpServer := server.NewMCPServer("name", "1.0.0", oauthOption)
219+
220+
streamableServer := server.NewStreamableHTTPServer(mcpServer, ...)
221+
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamableServer))
158222
```
159223

224+
**Note**: `WrapMCPEndpoint()` provides automatic 401 handling with proper WWW-Authenticate headers when Bearer token is missing. It also passes through OPTIONS requests (CORS) and non-Bearer auth schemes.
225+
160226
### With Official SDK
161227
```go
162228
import (
@@ -166,5 +232,35 @@ import (
166232

167233
mcpServer := mcp.NewServer(&mcp.Implementation{...}, nil)
168234
_, handler, _ := mcpoauth.WithOAuth(mux, &oauth.Config{...}, mcpServer)
169-
http.ListenAndServe(":8080", handler)
235+
http.ListenAndServe(":8080", handler) // 401 handling automatic
170236
```
237+
238+
**Note**: Official SDK adapter includes automatic 401 handling in the returned handler.
239+
240+
## Extending the Library
241+
242+
### Adding a New OAuth Provider
243+
244+
1. Add validator to `provider/provider.go` implementing `TokenValidator` interface
245+
2. Update `createValidator()` switch in `config.go`
246+
3. Add provider documentation in `docs/providers/`
247+
248+
### Adding a New SDK Adapter
249+
250+
1. Create `<sdk>/oauth.go` with `WithOAuth()` function
251+
2. Follow pattern: create `oauth.Server`, register handlers, return SDK-specific middleware/option
252+
3. Never import MCP SDKs in core package
253+
254+
### Adding New Endpoints
255+
256+
1. Add handler method to `OAuth2Handler` in `handlers.go`
257+
2. Register in `RegisterHandlers()` in `oauth.go`
258+
259+
## Documentation References
260+
261+
- `examples/README.md` - Complete setup guide with Okta configuration
262+
- `examples/mark3labs/` and `examples/official/` - Working examples (simple + advanced)
263+
- `docs/providers/*.md` - Provider-specific setup (OKTA.md, GOOGLE.md, AZURE.md, HMAC.md)
264+
- `docs/CONFIGURATION.md` - All configuration options
265+
- `docs/SECURITY.md` - Production best practices
266+
- `docs/TROUBLESHOOTING.md` - Common issues and solutions

README.md

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,15 @@ OAuth 2.1 authentication library for Go MCP servers.
1313
```go
1414
import "github.com/tuannvm/oauth-mcp-proxy/mark3labs"
1515

16-
_, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
16+
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
1717
Provider: "okta",
1818
Issuer: "https://your-company.okta.com",
1919
Audience: "api://your-mcp-server",
2020
})
2121

2222
mcpServer := server.NewMCPServer("Server", "1.0.0", oauthOption)
23+
streamable := server.NewStreamableHTTPServer(mcpServer, /*options*/)
24+
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamable))
2325
```
2426

2527
### Official SDK
@@ -45,6 +47,7 @@ http.ListenAndServe(":8080", handler)
4547

4648
- **Dual SDK support** - Works with both mark3labs and official SDKs
4749
- **Simple integration** - One `WithOAuth()` call protects all tools
50+
- **Automatic 401 handling** - RFC 6750 compliant error responses with OAuth discovery
4851
- **Zero per-tool config** - All tools automatically protected
4952
- **Fast token caching** - 5-min cache, <5ms validation
5053
- **Production ready** - Security hardened, battle-tested
@@ -149,7 +152,7 @@ import (
149152
mux := http.NewServeMux()
150153

151154
// Enable OAuth (one time setup)
152-
_, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
155+
oauthServer, oauthOption, _ := mark3labs.WithOAuth(mux, &oauth.Config{
153156
Provider: "okta", // or "hmac", "google", "azure"
154157
Issuer: "https://your-company.okta.com",
155158
Audience: "api://your-mcp-server",
@@ -162,12 +165,12 @@ mcpServer := mcpserver.NewMCPServer("Server", "1.0.0", oauthOption)
162165
// Add tools - all automatically protected
163166
mcpServer.AddTool(myTool, myHandler)
164167

165-
// Setup endpoint
168+
// Setup endpoint with automatic 401 handling
166169
streamable := mcpserver.NewStreamableHTTPServer(
167170
mcpServer,
168171
mcpserver.WithHTTPContextFunc(oauth.CreateHTTPContextFunc()),
169172
)
170-
mux.Handle("/mcp", streamable)
173+
mux.HandleFunc("/mcp", oauthServer.WrapMCPEndpoint(streamable))
171174
```
172175

173176
#### 3. Access Authenticated User
@@ -265,8 +268,8 @@ See [examples/README.md](examples/README.md) for detailed setup guide including
265268

266269
**Getting Started:**
267270

271+
- [Setup Guide](docs/CLIENT-SETUP.md) - Complete server integration and client configuration
268272
- [Configuration Guide](docs/CONFIGURATION.md) - All config options
269-
- [Client Setup](docs/CLIENT-SETUP.md) - Client configuration
270273
- [Provider Setup](docs/providers/) - OAuth provider guides
271274

272275
**Advanced:**

0 commit comments

Comments
 (0)