Skip to content

Commit 94d5f7a

Browse files
committed
support allowlist mode and fixed redirect mode simultaneously
Signed-off-by: Christian Troelsen <christian.troelsen@tryg.dk> format fix again
1 parent 97c75a7 commit 94d5f7a

3 files changed

Lines changed: 67 additions & 54 deletions

File tree

config.go

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,10 @@ import (
1111
// Config holds OAuth configuration
1212
type Config struct {
1313
// OAuth settings
14-
Mode string // "native" or "proxy"
15-
Provider string // "hmac", "okta", "google", "azure"
16-
RedirectURIs string // Redirect URIs (single or comma-separated)
14+
Mode string // "native" or "proxy"
15+
Provider string // "hmac", "okta", "google", "azure"
16+
RedirectURIs string // Redirect URIs allowlist (single or comma-separated)
17+
FixedRedirectURI string // Optional fixed redirect URI used for proxying callbacks
1718

1819
// OIDC configuration
1920
Issuer string
@@ -89,8 +90,8 @@ func (c *Config) Validate() error {
8990
if c.ServerURL == "" {
9091
return fmt.Errorf("proxy mode requires ServerURL")
9192
}
92-
if c.RedirectURIs == "" {
93-
return fmt.Errorf("proxy mode requires RedirectURIs")
93+
if c.RedirectURIs == "" && c.FixedRedirectURI == "" {
94+
return fmt.Errorf("proxy mode requires RedirectURIs or FixedRedirectURI")
9495
}
9596
}
9697

@@ -197,6 +198,12 @@ func (b *ConfigBuilder) WithRedirectURIs(uris string) *ConfigBuilder {
197198
return b
198199
}
199200

201+
// WithFixedRedirectURI sets the fixed redirect URI used for proxying callbacks
202+
func (b *ConfigBuilder) WithFixedRedirectURI(uri string) *ConfigBuilder {
203+
b.config.FixedRedirectURI = uri
204+
return b
205+
}
206+
200207
// WithIssuer sets the OIDC issuer
201208
func (b *ConfigBuilder) WithIssuer(issuer string) *ConfigBuilder {
202209
b.config.Issuer = issuer
@@ -319,6 +326,7 @@ func FromEnv() (*Config, error) {
319326
WithMode(getEnv("OAUTH_MODE", "")).
320327
WithProvider(getEnv("OAUTH_PROVIDER", "")).
321328
WithRedirectURIs(getEnv("OAUTH_REDIRECT_URIS", "")).
329+
WithFixedRedirectURI(getEnv("OAUTH_FIXED_REDIRECT_URI", "")).
322330
WithIssuer(getEnv("OIDC_ISSUER", "")).
323331
WithAudience(getEnv("OIDC_AUDIENCE", "")).
324332
WithClientID(getEnv("OIDC_CLIENT_ID", "")).

handlers.go

Lines changed: 47 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ type OAuth2Config struct {
4040
Mode string // "native" or "proxy"
4141
Provider string
4242
RedirectURIs string
43+
// FixedRedirectURI is an optional fixed redirect URI used when proxying callbacks
44+
FixedRedirectURI string
4345

4446
// OIDC configuration
4547
Issuer string
@@ -184,21 +186,22 @@ func NewOAuth2ConfigFromConfig(cfg *Config, version string) *OAuth2Config {
184186
}
185187

186188
return &OAuth2Config{
187-
Enabled: true,
188-
Mode: cfg.Mode,
189-
Provider: cfg.Provider,
190-
RedirectURIs: cfg.RedirectURIs,
191-
Issuer: cfg.Issuer,
192-
Audience: cfg.Audience,
193-
ClientID: cfg.ClientID,
194-
ClientSecret: cfg.ClientSecret,
195-
Scopes: scopes,
196-
MCPHost: mcpHost,
197-
MCPPort: mcpPort,
198-
MCPURL: mcpURL,
199-
Scheme: scheme,
200-
Version: version,
201-
stateSigningKey: cfg.JWTSecret,
189+
Enabled: true,
190+
Mode: cfg.Mode,
191+
Provider: cfg.Provider,
192+
RedirectURIs: cfg.RedirectURIs,
193+
FixedRedirectURI: cfg.FixedRedirectURI,
194+
Issuer: cfg.Issuer,
195+
Audience: cfg.Audience,
196+
ClientID: cfg.ClientID,
197+
ClientSecret: cfg.ClientSecret,
198+
Scopes: scopes,
199+
MCPHost: mcpHost,
200+
MCPPort: mcpPort,
201+
MCPURL: mcpURL,
202+
Scheme: scheme,
203+
Version: version,
204+
stateSigningKey: cfg.JWTSecret,
202205
}
203206
}
204207

@@ -298,12 +301,22 @@ func (h *OAuth2Handler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
298301

299302
// Determine redirect URI strategy based on configuration
300303
var redirectURI string
301-
hasFixedRedirect := h.config.RedirectURIs != "" && !strings.Contains(h.config.RedirectURIs, ",")
304+
actualState := state
305+
306+
if h.config.RedirectURIs != "" {
307+
// Allowlist mode: Client's URI must be in allowlist, used directly (no proxy)
308+
if h.isValidRedirectURI(clientRedirectURI) {
309+
redirectURI = clientRedirectURI
310+
h.logger.Info("OAuth2: Allowlist mode - using client URI from allowlist: %s", redirectURI)
311+
} else {
312+
h.logger.Warn("SECURITY: Redirect URI not in allowlist: %s from %s", clientRedirectURI, r.RemoteAddr)
313+
h.logger.Info("SECURITY: Will try fixed redirect mode next")
314+
}
315+
}
302316

303-
if hasFixedRedirect {
317+
if redirectURI == "" && h.config.FixedRedirectURI != "" {
304318
// Fixed redirect mode: Use server's redirect URI to OAuth provider, proxy back to client
305-
redirectURI = strings.TrimSpace(h.config.RedirectURIs)
306-
h.logger.Info("OAuth2: Fixed redirect mode - using server URI: %s (will proxy to client: %s)", redirectURI, clientRedirectURI)
319+
h.logger.Info("OAuth2: Fixed redirect mode - using server URI: %s (will proxy to client: %s)", h.config.FixedRedirectURI, clientRedirectURI)
307320

308321
// Validate client redirect URI format and security
309322
if clientRedirectURI == "" {
@@ -347,30 +360,9 @@ func (h *OAuth2Handler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
347360
http.Error(w, "Fixed redirect mode only allows localhost redirect URIs for security. Use allowlist mode for production.", http.StatusBadRequest)
348361
return
349362
}
350-
363+
redirectURI = strings.TrimSpace(h.config.FixedRedirectURI)
351364
h.logger.Info("OAuth2: Validated localhost redirect URI for proxy: %s", clientRedirectURI)
352-
} else if h.config.RedirectURIs != "" {
353-
// Allowlist mode: Client's URI must be in allowlist, used directly (no proxy)
354-
if !h.isValidRedirectURI(clientRedirectURI) {
355-
h.logger.Warn("SECURITY: Redirect URI not in allowlist: %s from %s", clientRedirectURI, r.RemoteAddr)
356-
http.Error(w, "Invalid redirect_uri", http.StatusBadRequest)
357-
return
358-
}
359-
redirectURI = clientRedirectURI
360-
h.logger.Info("OAuth2: Allowlist mode - using client URI from allowlist: %s", redirectURI)
361-
} else {
362-
// No configuration: Reject for security
363-
h.logger.Warn("SECURITY: No redirect URIs configured, rejecting: %s from %s", clientRedirectURI, r.RemoteAddr)
364-
http.Error(w, "Invalid redirect_uri", http.StatusBadRequest)
365-
return
366-
}
367-
368-
// Update OAuth2 config with redirect URI
369-
h.oauth2Config.RedirectURL = redirectURI
370-
371-
// For fixed redirect mode, create signed state with client redirect URI
372-
actualState := state
373-
if hasFixedRedirect {
365+
// For fixed redirect mode, create signed state with client redirect URI
374366
// Create state data with redirect URI
375367
stateData := map[string]string{
376368
"state": state,
@@ -389,6 +381,16 @@ func (h *OAuth2Handler) HandleAuthorize(w http.ResponseWriter, r *http.Request)
389381
h.logger.Info("OAuth2: Signed state for proxy callback (length: %d)", len(signedState))
390382
}
391383

384+
if redirectURI == "" {
385+
// No configuration: Reject for security
386+
h.logger.Warn("SECURITY: No redirect URIs configured, rejecting: %s from %s", clientRedirectURI, r.RemoteAddr)
387+
http.Error(w, "Invalid redirect_uri", http.StatusBadRequest)
388+
return
389+
}
390+
391+
// Update OAuth2 config with redirect URI
392+
h.oauth2Config.RedirectURL = redirectURI
393+
392394
// Create authorization URL
393395
authURL := h.oauth2Config.AuthCodeURL(actualState, oauth2.AccessTypeOffline)
394396

@@ -449,7 +451,7 @@ func (h *OAuth2Handler) HandleCallback(w http.ResponseWriter, r *http.Request) {
449451
}
450452

451453
// If using fixed redirect URI, handle proxy callback
452-
if h.config.RedirectURIs != "" && !strings.Contains(h.config.RedirectURIs, ",") {
454+
if h.config.FixedRedirectURI != "" {
453455
// Verify and decode signed state parameter
454456
stateData, err := h.verifyState(state)
455457
if err != nil {
@@ -546,8 +548,8 @@ func (h *OAuth2Handler) HandleToken(w http.ResponseWriter, r *http.Request) {
546548

547549
// Set redirect URI for token exchange
548550
redirectURI := clientRedirectURI
549-
if h.config.RedirectURIs != "" && !strings.Contains(h.config.RedirectURIs, ",") {
550-
redirectURI = strings.TrimSpace(h.config.RedirectURIs)
551+
if h.config.FixedRedirectURI != "" && !h.isValidRedirectURI(clientRedirectURI) {
552+
redirectURI = strings.TrimSpace(h.config.FixedRedirectURI)
551553
h.logger.Info("OAuth2: Token exchange using fixed redirect URI: %s", redirectURI)
552554
}
553555

@@ -802,8 +804,6 @@ func isLocalhostURI(uri string) bool {
802804
// isValidRedirectURI validates redirect URI against allowlist for security
803805
func (h *OAuth2Handler) isValidRedirectURI(uri string) bool {
804806
if h.config.RedirectURIs == "" {
805-
// No redirect URIs configured - reject all redirects for security
806-
h.logger.Warn("WARNING: No OAuth redirect URIs configured, rejecting redirect: %s", uri)
807807
return false
808808
}
809809

metadata.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,11 +194,16 @@ func (h *OAuth2Handler) HandleRegister(w http.ResponseWriter, r *http.Request) {
194194
if redirectUris, ok := regRequest["redirect_uris"]; ok {
195195
response["redirect_uris"] = redirectUris
196196
h.logger.Info("OAuth2: Registration allowing client redirect URIs: %v", redirectUris)
197+
} else if h.config.FixedRedirectURI != "" {
198+
// Fallback to fixed redirect URI if no client URIs provided
199+
trimmedURI := strings.TrimSpace(h.config.FixedRedirectURI)
200+
response["redirect_uris"] = []string{trimmedURI}
201+
h.logger.Info("OAuth2: Registration response using fixed redirect URI: %s", trimmedURI)
197202
} else if h.config.RedirectURIs != "" && !strings.Contains(h.config.RedirectURIs, ",") {
198-
// Fallback to fixed redirect URI if no client URIs provided (single URI only)
203+
// Backwards-compatible fallback: single RedirectURIs value
199204
trimmedURI := strings.TrimSpace(h.config.RedirectURIs)
200205
response["redirect_uris"] = []string{trimmedURI}
201-
h.logger.Info("OAuth2: Registration response using fixed redirect URI: %s", trimmedURI)
206+
h.logger.Info("OAuth2: Registration response using single redirect URI from RedirectURIs: %s", trimmedURI)
202207
}
203208

204209
w.Header().Set("Content-Type", "application/json")

0 commit comments

Comments
 (0)