semaphoreui/semaphore · error

OIDC sign-in failed: invalid redirect URL.

Error message

OIDC sign-in failed: invalid redirect URL.

What it means

oidcSuccessRedirectURL (api/login.go:835) builds the post-login redirect by joining WebHost with the redirect path ('/'+trimmed path when ReturnViaState is false, else the state-returned path). If that join returns an error (malformed WebHost / redirect path producing an invalid URL), oidcRedirect responds 500 'OIDC sign-in failed: invalid redirect URL.'

Solutions

  1. Check web_host in the config is a valid absolute URL (scheme + host), e.g. https://semaphore.example.com.
  2. Inspect the server log line for the underlying url.JoinPath error to see the offending path value.
  3. If ReturnViaState is enabled, verify the 'return' value stored in state is a sane relative path; sanitize it.
  4. Clear cookies and retry the login flow so fresh, valid state is generated.

Example fix

# before
web_host: "semaphore.example.com"   # no scheme -> invalid URL when joined
// after
web_host: "https://semaphore.example.com"
Defensive patterns

Strategy: validation

Validate before calling

// sanity-check web_host before serving login
function validWebHost(u) { try { const x = new URL(u); return x.protocol.startsWith("http"); } catch { return false; } }
if (!validWebHost(config.web_host)) alert("web_host must be an absolute http(s) URL");

Type guard

function isSafeRedirectPath(p) {
  return typeof p === "string" && p.length > 0 && p.startsWith("/") && !p.startsWith("//") && !/[<>\"'\\]/.test(p);
}

Try / catch

try {
  await finishOidcLogin(state, code);
} catch (e) {
  if (e.status === 500 && /invalid redirect URL/.test(e.body)) {
    // fall back to web root and report config issue
    window.location.assign("/");
  }
}

Prevention

When it happens

Trigger: After successful auth, config.ReturnViaState routes stateData.Return, or mux var redirect_path, into oidcSuccessRedirectURL(util.Config.WebHost, redirectPath); url.JoinPath errors because web_host is malformed (e.g. contains invalid characters or unparseable URL) or the redirect path/state value is corrupt.

Common situations: web_host set to something that is not a valid absolute URL (missing scheme, stray characters); a tampered or corrupted state 'return' value; redirect_path mux variable carrying percent-encoded garbage; upgrades where web_host was left empty vs set inconsistently.

Understand the failure class

Background: "Invalid URL" / "URL cannot be empty": fix the malformed or missing URL behind request-construction failures — this error's family across 50 libraries.

Related errors


AI-assisted analysis of semaphoreui/semaphore@1774ccb71a (2026-09-07). Data as JSON: /api/errors/600c9ce586f556b3. Report an issue: GitHub.

Appendix: source

Thrown at api/login.go:1028

	config, ok := util.Config.OidcProviders[pid]
	if !ok {
		log.Error(fmt.Errorf("no such provider: %s", pid))
		http.Error(w, "Unknown OIDC provider.", http.StatusNotFound)
		return
	}

	redirectPath := ""
	if config.ReturnViaState {
		redirectPath = stateData.Return
	} else {
		redirectPath = mux.Vars(r)["redirect_path"]
	}

	redirectURL, err := oidcSuccessRedirectURL(util.Config.WebHost, redirectPath)
	if err != nil {
		log.Error(err)
		http.Error(w, "OIDC sign-in failed: invalid redirect URL.", http.StatusInternalServerError)
		return
	}

	http.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)
}

View on GitHub (pinned to 1774ccb71a)