-
Notifications
You must be signed in to change notification settings - Fork 6
Add more config variables #9
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,10 @@ package oauth | |
|
|
||
| import ( | ||
| "fmt" | ||
| "strconv" | ||
| "strings" | ||
|
|
||
| "github.com/golang-jwt/jwt/v5" | ||
| "github.com/tuannvm/oauth-mcp-proxy/provider" | ||
| ) | ||
|
|
||
|
|
@@ -18,6 +21,7 @@ type Config struct { | |
| Audience string | ||
| ClientID string | ||
| ClientSecret string | ||
| Scopes []string | ||
|
|
||
| // Server configuration | ||
| ServerURL string // Full URL of the MCP server | ||
|
|
@@ -31,6 +35,12 @@ type Config struct { | |
| // Implement the Logger interface (Debug, Info, Warn, Error methods) to | ||
| // integrate with your application's logging system (e.g., zap, logrus). | ||
| Logger Logger | ||
|
|
||
| // Token validation configuration | ||
| SkipIssuerCheck bool | ||
| SkipAudienceCheck bool | ||
| SkipExpiryCheck bool | ||
| TokenValidationFuncs []func(claims jwt.MapClaims) error | ||
|
Comment on lines
+39
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Add comprehensive documentation for token validation configuration. These security-sensitive fields need detailed documentation explaining their purpose, security implications, and proper usage. The section comment "Token validation configuration" is insufficient for fields that can weaken security when misconfigured. // Token validation configuration
+ //
+ // SkipIssuerCheck bypasses issuer validation in OIDC tokens.
+ // WARNING: Only use in development or when implementing custom issuer validation.
+ // Skipping issuer checks allows tokens from any issuer to be accepted.
SkipIssuerCheck bool
+
+ // SkipAudienceCheck bypasses audience validation in OIDC tokens.
+ // WARNING: Only use in specific multi-tenant scenarios with custom audience validation.
+ // Skipping audience checks allows tokens intended for other services to be accepted.
SkipAudienceCheck bool
+
+ // SkipExpiryCheck bypasses expiration validation in OIDC tokens.
+ // WARNING: Only use for testing. Production systems should never accept expired tokens.
SkipExpiryCheck bool
+
+ // TokenValidationFuncs provides custom validation logic that runs after standard validation.
+ // Each function receives raw token claims and should return an error if validation fails.
+ // This field must be set programmatically (cannot be configured via environment variables).
+ // Use this to implement application-specific validation (e.g., custom claims, role checks).
TokenValidationFuncs []func(claims jwt.MapClaims) error🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // Validate validates the configuration | ||
|
|
@@ -119,11 +129,15 @@ func SetupOAuth(cfg *Config) (provider.TokenValidator, error) { | |
| func createValidator(cfg *Config, logger Logger) (provider.TokenValidator, error) { | ||
| // Convert root Config to provider.Config | ||
| providerCfg := &provider.Config{ | ||
| Provider: cfg.Provider, | ||
| Issuer: cfg.Issuer, | ||
| Audience: cfg.Audience, | ||
| JWTSecret: cfg.JWTSecret, | ||
| Logger: logger, | ||
| Provider: cfg.Provider, | ||
| Issuer: cfg.Issuer, | ||
| Audience: cfg.Audience, | ||
| JWTSecret: cfg.JWTSecret, | ||
| Logger: logger, | ||
| SkipIssuerCheck: cfg.SkipIssuerCheck, | ||
| SkipAudienceCheck: cfg.SkipAudienceCheck, | ||
| SkipExpiryCheck: cfg.SkipExpiryCheck, | ||
| TokenValidatorFuncs: cfg.TokenValidationFuncs, | ||
| } | ||
|
|
||
| var validator provider.TokenValidator | ||
|
|
@@ -217,12 +231,36 @@ func (b *ConfigBuilder) WithJWTSecret(secret []byte) *ConfigBuilder { | |
| return b | ||
| } | ||
|
|
||
| // WithScopes sets the OIDC scopes | ||
| func (b *ConfigBuilder) WithScopes(scopes []string) *ConfigBuilder { | ||
| b.config.Scopes = scopes | ||
| return b | ||
| } | ||
|
|
||
| // WithLogger sets the logger | ||
| func (b *ConfigBuilder) WithLogger(logger Logger) *ConfigBuilder { | ||
| b.config.Logger = logger | ||
| return b | ||
| } | ||
|
|
||
| // WithSkipIssuerCheck sets issuer check toggle | ||
| func (b *ConfigBuilder) WithSkipIssuerCheck(skipIssuerCheck bool) *ConfigBuilder { | ||
| b.config.SkipIssuerCheck = skipIssuerCheck | ||
| return b | ||
| } | ||
|
|
||
| // WithSkipAudienceCheck sets audience check toggle | ||
| func (b *ConfigBuilder) WithSkipAudienceCheck(skipAudienceCheck bool) *ConfigBuilder { | ||
| b.config.SkipAudienceCheck = skipAudienceCheck | ||
| return b | ||
| } | ||
|
|
||
| // WithSkipExpiryCheck sets expiry check toggle | ||
| func (b *ConfigBuilder) WithSkipExpiryCheck(skipExpiryCheck bool) *ConfigBuilder { | ||
| b.config.SkipExpiryCheck = skipExpiryCheck | ||
| return b | ||
| } | ||
|
|
||
| // WithServerURL sets the full server URL directly | ||
| func (b *ConfigBuilder) WithServerURL(url string) *ConfigBuilder { | ||
| b.config.ServerURL = url | ||
|
|
@@ -281,6 +319,12 @@ func FromEnv() (*Config, error) { | |
|
|
||
| jwtSecret := getEnv("JWT_SECRET", "") | ||
|
|
||
| scopes := []string{} | ||
| scopesEnv := getEnv("OIDC_SCOPES", "") | ||
| if scopesEnv != "" { | ||
| scopes = strings.Split(scopesEnv, " ") | ||
| } | ||
|
|
||
| return NewConfigBuilder(). | ||
| WithMode(getEnv("OAUTH_MODE", "")). | ||
| WithProvider(getEnv("OAUTH_PROVIDER", "")). | ||
|
|
@@ -289,7 +333,24 @@ func FromEnv() (*Config, error) { | |
| WithAudience(getEnv("OIDC_AUDIENCE", "")). | ||
| WithClientID(getEnv("OIDC_CLIENT_ID", "")). | ||
| WithClientSecret(getEnv("OIDC_CLIENT_SECRET", "")). | ||
| WithScopes(scopes). | ||
| WithSkipAudienceCheck(parseBoolEnv("OIDC_SKIP_AUDIENCE_CHECK", false)). | ||
| WithSkipIssuerCheck(parseBoolEnv("OIDC_SKIP_ISSUER_CHECK", false)). | ||
| WithSkipExpiryCheck(parseBoolEnv("OIDC_SKIP_EXPIRY_CHECK", false)). | ||
| WithServerURL(serverURL). | ||
| WithJWTSecret([]byte(jwtSecret)). | ||
| Build() | ||
| } | ||
|
|
||
| // parseBoolEnv parses a boolean environment variable | ||
| func parseBoolEnv(key string, defaultVal bool) bool { | ||
| val := getEnv(key, "") | ||
| if val == "" { | ||
| return defaultVal | ||
| } | ||
| parsed, err := strconv.ParseBool(val) | ||
| if err != nil { | ||
| return defaultVal | ||
| } | ||
| return parsed | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -30,11 +30,15 @@ type Logger interface { | |
|
|
||
| // Config holds OAuth configuration (subset needed by provider) | ||
| type Config struct { | ||
| Provider string | ||
| Issuer string | ||
| Audience string | ||
| JWTSecret []byte | ||
| Logger Logger | ||
| Provider string | ||
| Issuer string | ||
| Audience string | ||
| JWTSecret []byte | ||
| Logger Logger | ||
| SkipIssuerCheck bool | ||
| SkipAudienceCheck bool | ||
| SkipExpiryCheck bool | ||
| TokenValidatorFuncs []func(claims jwt.MapClaims) error | ||
|
Comment on lines
+33
to
+41
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion | 🟠 Major Add documentation for security-sensitive configuration fields. The newly added fields ( Add documentation comments explaining:
Example: // Config holds OAuth configuration (subset needed by provider)
type Config struct {
Provider string
Issuer string
Audience string
JWTSecret []byte
Logger Logger
// SkipIssuerCheck bypasses issuer validation in OIDC tokens.
// WARNING: Only use this in development or when implementing custom issuer validation.
// Skipping issuer checks allows tokens from any issuer to be accepted.
SkipIssuerCheck bool
// SkipAudienceCheck bypasses audience validation in OIDC tokens.
// WARNING: Only use this in specific multi-tenant scenarios with custom audience validation.
// Skipping audience checks allows tokens intended for other services to be accepted.
SkipAudienceCheck bool
// SkipExpiryCheck bypasses expiration validation in OIDC tokens.
// WARNING: Only use this for testing. Production systems should never accept expired tokens.
SkipExpiryCheck bool
// TokenValidatorFuncs provides custom validation logic that runs after standard OIDC validation.
// Each function receives the raw token claims and should return an error if validation fails.
// Use this to implement application-specific claim validation (e.g., checking custom claims, roles).
TokenValidatorFuncs []func(claims jwt.MapClaims) error
}🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| // TokenValidator interface for OAuth token validation | ||
|
|
@@ -52,10 +56,11 @@ type HMACValidator struct { | |
|
|
||
| // OIDCValidator validates JWT tokens using OIDC/JWKS (Okta, Google, Azure) | ||
| type OIDCValidator struct { | ||
| verifier *oidc.IDTokenVerifier | ||
| provider *oidc.Provider | ||
| audience string | ||
| logger Logger | ||
| verifier *oidc.IDTokenVerifier | ||
| provider *oidc.Provider | ||
| audience string | ||
| TokenValidatorFuncs []func(claims jwt.MapClaims) error | ||
| logger Logger | ||
| } | ||
|
|
||
| // Initialize sets up the HMAC validator with JWT secret and audience | ||
|
|
@@ -90,7 +95,6 @@ func (v *HMACValidator) ValidateToken(ctx context.Context, tokenString string) ( | |
| } | ||
| return []byte(v.secret), nil | ||
| }) | ||
|
|
||
| if err != nil { | ||
| return nil, fmt.Errorf("failed to parse and validate token: %w", err) | ||
| } | ||
|
|
@@ -204,15 +208,16 @@ func (v *OIDCValidator) Initialize(cfg *Config) error { | |
| verifier := provider.Verifier(&oidc.Config{ | ||
| ClientID: cfg.Audience, // Note: go-oidc uses ClientID field for audience validation - see https://github.com/coreos/go-oidc/blob/v3/oidc/verify.go#L85 | ||
| SupportedSigningAlgs: []string{oidc.RS256, oidc.ES256}, | ||
| SkipClientIDCheck: false, // Always validate if ClientID is provided | ||
| SkipExpiryCheck: false, // Verify expiration | ||
| SkipIssuerCheck: false, // Verify issuer | ||
| SkipClientIDCheck: cfg.SkipAudienceCheck, | ||
| SkipExpiryCheck: cfg.SkipExpiryCheck, | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
| SkipIssuerCheck: cfg.SkipIssuerCheck, | ||
| }) | ||
|
|
||
| v.logger.Info("OAuth: OIDC validator initialized with audience validation: %s", cfg.Audience) | ||
|
|
||
| v.provider = provider | ||
| v.verifier = verifier | ||
| v.TokenValidatorFuncs = cfg.TokenValidatorFuncs | ||
| return nil | ||
| } | ||
|
|
||
|
|
@@ -261,6 +266,14 @@ func (v *OIDCValidator) ValidateToken(ctx context.Context, tokenString string) ( | |
| return nil, fmt.Errorf("audience validation failed: %w", err) | ||
| } | ||
|
|
||
| // Run extra validation functions | ||
| for _, fn := range v.TokenValidatorFuncs { | ||
| err := fn(rawClaims) | ||
| if err != nil { | ||
| return nil, fmt.Errorf("validation function failed with error: %w", err) | ||
| } | ||
| } | ||
|
|
||
| return &User{ | ||
| Subject: claims.Subject, | ||
| Username: claims.PreferredUsername, | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🛠️ Refactor suggestion | 🟠 Major
Add documentation for Scopes field.
The
Scopesfield lacks documentation explaining its purpose and default behavior. Users need to understand what scopes are used when this field is empty.📝 Committable suggestion
🤖 Prompt for AI Agents