{"record":{"id":"4b79d1fdeb329062","repo":"juanfont/headscale","slug":"errinvalidpkcemethod","errorCode":"errInvalidPKCEMethod","errorMessage":"internal server error","messagePattern":"internal server error","errorType":"http","errorClass":"HTTPError","httpStatus":500,"severity":"error","filePath":"hscontrol/oidc.go","lineNumber":196,"sourceCode":"\textras := make([]oauth2.AuthCodeOption, 0, len(a.cfg.ExtraParams)+defaultOAuthOptionsCount)\n\t// Add PKCE verification if enabled\n\tif a.cfg.PKCE.Enabled {\n\t\tverifier := oauth2.GenerateVerifier()\n\t\tregistrationInfo.Verifier = &verifier\n\n\t\textras = append(extras, oauth2.AccessTypeOffline)\n\n\t\tswitch a.cfg.PKCE.Method {\n\t\tcase types.PKCEMethodS256:\n\t\t\textras = append(extras, oauth2.S256ChallengeOption(verifier))\n\t\tcase types.PKCEMethodPlain:\n\t\t\t// oauth2 does not have a plain challenge option, so we add it manually\n\t\t\textras = append(extras, oauth2.SetAuthURLParam(\"code_challenge_method\", \"plain\"), oauth2.SetAuthURLParam(\"code_challenge\", verifier))\n\t\tdefault:\n\t\t\t// An unknown method must not silently emit no challenge: a\n\t\t\t// verifier was generated and is sent at token exchange, so a\n\t\t\t// missing challenge degrades to no-PKCE without anyone noticing.\n\t\t\thttpError(writer, NewHTTPError(http.StatusInternalServerError, \"internal server error\", fmt.Errorf(\"%w: %q\", errInvalidPKCEMethod, a.cfg.PKCE.Method)))\n\t\t\treturn\n\t\t}\n\t}\n\n\t// Add any extra parameters from configuration\n\tfor k, v := range a.cfg.ExtraParams {\n\t\textras = append(extras, oauth2.SetAuthURLParam(k, v))\n\t}\n\n\textras = append(extras, oidc.Nonce(nonce))\n\n\t// Cache the registration info\n\ta.authCache.Add(state, registrationInfo)\n\n\tauthURL := a.oauth2Config.AuthCodeURL(state, extras...)\n\tlog.Debug().Caller().Msgf(\"redirecting to %s for authentication\", authURL)\n\n\thttp.Redirect(writer, req, authURL, http.StatusFound)","sourceCodeStart":178,"sourceCodeEnd":214,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/oidc.go#L178-L214","documentation":"Returned by the OIDC login redirect handler when a.cfg.PKCE.Method is neither 'S256' nor 'plain' (errInvalidPKCEMethod, 500). A verifier was already generated and would be sent at token exchange, so silently skipping the challenge would degrade to no-PKCE without notice — hence the hard failure instead of a silent omission.","triggerScenarios":"Configuring oidc.pkce.method to a value outside {S256, plain} (case-sensitive), or leaving an experimental/typo value like 's256' or 'SHA256'.","commonSituations":"YAML config typo including wrong case or whitespace; upgrading headscale where accepted PKCE method names changed; copy-pasted config from an example using an invalid value.","solutions":["Set oidc.pkce.method to exactly S256 or plain (or remove it to use the default)","Check the value for stray whitespace/quotes in the YAML","Consult the provider's docs: most modern providers require S256"],"exampleFix":"# before\noidc:\n  pkce:\n    method: s256\n\n# after\noidc:\n  pkce:\n    method: S256","handlingStrategy":"validation","validationCode":"switch cfg.PKCE.Method {\ncase types.PKCEMethodS256, types.PKCEMethodPlain:\ndefault:\n    return fmt.Errorf(\"invalid oidc.pkce.method %q: must be S256 or plain\", cfg.PKCE.Method)\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Validate config enums at load time, not at request time — fail startup on unknown PKCE method","Document accepted values in deployment runbooks to avoid case-sensitivity typos"],"tags":["oidc","pkce","config","validation"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}