netbirdio/netbird · error

failed to parse redirect URL: %v

Error message

failed to parse redirect URL: %v

What it means

Returned by PKCEAuthorizationFlow.WaitToken when url.Parse rejects the selected redirect URL (client/internal/auth/pkce_flow.go:196-199). The parsed URL's port is needed to bind the local callback server (http.Server on ':'+port). url.Parse fails only on truly malformed input - ASCII control characters or invalid percent-encoding - so in practice this means the redirect URL stored in the management/IdP configuration is malformed. Note the URL already passed isRedirectURLPortUsed during flow construction, which also parses and would have logged a parse failure there.

Source

Thrown at client/internal/auth/pkce_flow.go:198

// WaitToken waits for the OAuth token in the PKCE Authorization Flow.
// It starts an HTTP server to receive the OAuth token callback and waits for the token or an error.
// Once the token is received, it is converted to TokenInfo and validated before returning.
// The method creates a timeout context internally based on info.ExpiresIn.
func (p *PKCEAuthorizationFlow) WaitToken(ctx context.Context, info AuthFlowInfo) (TokenInfo, error) {
	// Create timeout context based on flow expiration
	timeout := time.Duration(info.ExpiresIn) * time.Second
	waitCtx, cancel := context.WithTimeout(ctx, timeout)
	defer cancel()

	log.Infof("pkce flow: waiting for authorization callback on %s, timeout %s", p.oAuthConfig.RedirectURL, timeout)

	tokenChan := make(chan *oauth2.Token, 1)
	errChan := make(chan error, 1)

	parsedURL, err := url.Parse(p.oAuthConfig.RedirectURL)
	if err != nil {
		return TokenInfo{}, fmt.Errorf("failed to parse redirect URL: %v", err)
	}

	server := &http.Server{Addr: fmt.Sprintf(":%s", parsedURL.Port())}
	defer func() {
		shutdownCtx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
		defer cancel()

		if err := server.Shutdown(shutdownCtx); err != nil {
			log.Errorf("failed to close the server: %v", err)
		}
	}()

	go p.startServer(server, tokenChan, errChan)

	select {
	case <-waitCtx.Done():
		return TokenInfo{}, waitCtx.Err()
	case token := <-tokenChan:

View on GitHub (pinned to 93e97f4bf1)

Solutions

  1. Administrator: re-enter the redirect URL in the management/IdP configuration cleanly (e.g. http://localhost:53000/callback), avoiding pasted control characters
  2. Verify with a quick parse before deploying config changes (see validationCode)
  3. End user: retry after the corrected config is fetched from management

Example fix

// before: config with a stray control character
RedirectURLs: []string{"http://localhost:53000/callback\n"}

// after: clean URL
RedirectURLs: []string{"http://localhost:53000/callback"}
Defensive patterns

Strategy: validation

Validate before calling

// Administrator-side: validate redirect URLs before saving them into the IdP integration
func validRedirectURL(raw string) error {
	u, err := url.Parse(raw)
	if err != nil {
		return fmt.Errorf("redirect URL %q is not parseable: %w", raw, err)
	}
	if u.Port() == "" {
		return fmt.Errorf("redirect URL %q must include an explicit port", raw)
	}
	return nil
}

Try / catch

if err != nil && strings.Contains(err.Error(), "failed to parse redirect URL") {
	// malformed URL in management/IdP config: strip control characters (e.g. trailing newline) and re-save
}

Prevention

When it happens

Trigger: p.oAuthConfig.RedirectURL contains control characters (e.g. a stray newline or NUL pasted into the admin config) or an invalid escape like '%zz'. url.Parse errors where a hostname would not: most typo'd URLs (missing scheme, bad port text) still parse, only control chars and broken percent-escapes do not.

Common situations: Copy-paste artifacts in the IdP integration config on management (trailing newline, hidden whitespace/control char), or programmatic config writes that injected unescaped characters.

Understand the failure class

Related errors


AI-assisted analysis of netbirdio/netbird@93e97f4bf1 (2026-08-16). Data as JSON: /api/errors/0cce12b3f7b073a7. Report an issue: GitHub.