{"record":{"id":"63fd931dedb4bd1b","repo":"semaphoreui/semaphore","slug":"account-linking-must-be-initiated-with-a-post-requ","errorCode":null,"errorMessage":"Account linking must be initiated with a POST request.","messagePattern":"Account linking must be initiated with a POST request\\.","errorType":"http","errorClass":null,"httpStatus":405,"severity":"error","filePath":"api/login.go","lineNumber":581,"sourceCode":"\treturnPath := \"\"\n\tredirectPath := \"\"\n\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\tlinkMode := r.URL.Query().Get(\"link\") != \"\"\n\n\tif linkMode {\n\t\t// POST-only: SameSite=Lax attaches the session cookie to top-level\n\t\t// cross-site GET navigations, so a GET here would let an attacker\n\t\t// initiate linking (CSRF) and attach their IdP identity to the\n\t\t// victim's account. Lax never sends the cookie on cross-site POST.\n\t\tif r.Method != http.MethodPost {\n\t\t\thttp.Error(w, \"Account linking must be initiated with a POST request.\", http.StatusMethodNotAllowed)\n\t\t\treturn\n\t\t}\n\t\tsession, ok := getSession(r)\n\t\tif !ok || !session.IsVerified() {\n\t\t\thttp.Error(w, \"You must be signed in to link an external account.\", http.StatusUnauthorized)\n\t\t\treturn\n\t\t}\n\t}\n\n\treturnValue := r.URL.Query().Get(\"return\")\n\tif returnValue != \"\" {\n\t\tif config.ReturnViaState {\n\t\t\treturnPath = returnValue\n\t\t} else {\n\t\t\tredirectPath = returnValue\n\t\t}\n\t}\n","sourceCodeStart":563,"sourceCodeEnd":599,"githubUrl":"https://github.com/semaphoreui/semaphore/blob/1774ccb71a0a8b82eb74ea24c23ac9ab713de2fa/api/login.go#L563-L599","documentation":"When the OIDC login is initiated in account-linking mode (?link=<provider>), the handler requires the HTTP POST method. This is a CSRF protection: the session cookie is SameSite=Lax, which is attached to cross-site GET navigations, so allowing GET here would let an attacker initiate linking and attach their IdP identity to a victim's account. A GET (or any non-POST) linking request returns HTTP 405 with this message.","triggerScenarios":"GET /api/auth/oidc/{pid}?link=true (or ?link=anything) with any method other than POST; following a plain link or redirect into the linking endpoint instead of submitting the linking form.","commonSituations":"Frontend building the link-account URL as an <a href> instead of a POST form/fetch; bookmarking the linking URL and reopening it later; antivirus/link-preview bots prefetching the GET URL; an attempted CSRF attack (which this check exists to block).","solutions":["Initiate account linking with an HTTP POST (form submit or fetch/XHR) to /api/auth/oidc/{pid}?link=...","Change the UI to use a form/button performing POST rather than a plain anchor link","Ensure the POST includes the session cookie (credentials: 'include' for fetch cross-origin)","If this is a prefetch by a bot, it is expected behavior — the request should not be a GET"],"exampleFix":"// before\n<a href=\"/api/auth/oidc/github?link=true\">Link GitHub</a>\n// after\n<form method=\"POST\" action=\"/api/auth/oidc/github?link=true\">\n  <button type=\"submit\">Link GitHub</button>\n</form>","handlingStrategy":"validation","validationCode":"// ensure POST before navigating to the link URL:\nif linkMode {\n    // must use a form/fetch POST, never window.location or <a href>\n    fetch(`/api/auth/oidc/${pid}?link=true`, { method: 'POST', credentials: 'include' })\n}","typeGuard":null,"tryCatchPattern":"const resp = await fetch(`/api/auth/oidc/${pid}?link=true`, { method: 'POST', credentials: 'include' })\nif (resp.status === 405) {\n    // request was sent as GET (or method overridden) — switch to a real POST form\n}","preventionTips":["Always render account-linking as a POST form/button, never a plain anchor","Check DevTools Network tab that the method is POST with cookies attached","Remember SameSite=Lax: cross-site POSTs won't carry the session cookie by design","Warn link-preview bots/prefetchers via X-Robots-Tag or rel=nofollow on linking UI"],"tags":["http","oidc","csrf","method-not-allowed"],"backgroundTag":"http-error-response","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"}