{"record":{"id":"600c9ce586f556b3","repo":"semaphoreui/semaphore","slug":"oidc-sign-in-failed-invalid-redirect-url","errorCode":null,"errorMessage":"OIDC sign-in failed: invalid redirect URL.","messagePattern":"OIDC sign-in failed: invalid redirect URL\\.","errorType":"http","errorClass":null,"httpStatus":500,"severity":"error","filePath":"api/login.go","lineNumber":1028,"sourceCode":"\n\tconfig, ok := util.Config.OidcProviders[pid]\n\tif !ok {\n\t\tlog.Error(fmt.Errorf(\"no such provider: %s\", pid))\n\t\thttp.Error(w, \"Unknown OIDC provider.\", http.StatusNotFound)\n\t\treturn\n\t}\n\n\tredirectPath := \"\"\n\tif config.ReturnViaState {\n\t\tredirectPath = stateData.Return\n\t} else {\n\t\tredirectPath = mux.Vars(r)[\"redirect_path\"]\n\t}\n\n\tredirectURL, err := oidcSuccessRedirectURL(util.Config.WebHost, redirectPath)\n\tif err != nil {\n\t\tlog.Error(err)\n\t\thttp.Error(w, \"OIDC sign-in failed: invalid redirect URL.\", http.StatusInternalServerError)\n\t\treturn\n\t}\n\n\thttp.Redirect(w, r, redirectURL, http.StatusTemporaryRedirect)\n}\n","sourceCodeStart":1010,"sourceCodeEnd":1034,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/login.go#L1010-L1034","documentation":"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.'","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check web_host in the config is a valid absolute URL (scheme + host), e.g. https://semaphore.example.com.","Inspect the server log line for the underlying url.JoinPath error to see the offending path value.","If ReturnViaState is enabled, verify the 'return' value stored in state is a sane relative path; sanitize it.","Clear cookies and retry the login flow so fresh, valid state is generated."],"exampleFix":"# before\nweb_host: \"semaphore.example.com\"   # no scheme -> invalid URL when joined\n// after\nweb_host: \"https://semaphore.example.com\"","handlingStrategy":"validation","validationCode":"// sanity-check web_host before serving login\nfunction validWebHost(u) { try { const x = new URL(u); return x.protocol.startsWith(\"http\"); } catch { return false; } }\nif (!validWebHost(config.web_host)) alert(\"web_host must be an absolute http(s) URL\");","typeGuard":"function isSafeRedirectPath(p) {\n  return typeof p === \"string\" && p.length > 0 && p.startsWith(\"/\") && !p.startsWith(\"//\") && !/[<>\\\"'\\\\]/.test(p);\n}","tryCatchPattern":"try {\n  await finishOidcLogin(state, code);\n} catch (e) {\n  if (e.status === 500 && /invalid redirect URL/.test(e.body)) {\n    // fall back to web root and report config issue\n    window.location.assign(\"/\");\n  }\n}","preventionTips":["Always set web_host as a full https:// URL","Sanitize the state 'return' value server-side to a relative path","Add a smoke test that completes an OIDC login and asserts the redirect resolves","Validate config at startup so malformed web_host fails fast"],"tags":["oidc","url","configuration","http-500"],"backgroundTag":"invalid-url","analyzedSha":"1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa","analyzedAt":"2026-09-07T11:00:33.293Z","contentChangedAt":"2026-09-07T11:00:33.293Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}