juanfont/headscale · error · HTTPError
errInvalidPKCEMethod
errInvalidPKCEMethod
Error message
internal server error
What it means
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.
Source
Thrown at hscontrol/oidc.go:196
extras := make([]oauth2.AuthCodeOption, 0, len(a.cfg.ExtraParams)+defaultOAuthOptionsCount)
// Add PKCE verification if enabled
if a.cfg.PKCE.Enabled {
verifier := oauth2.GenerateVerifier()
registrationInfo.Verifier = &verifier
extras = append(extras, oauth2.AccessTypeOffline)
switch a.cfg.PKCE.Method {
case types.PKCEMethodS256:
extras = append(extras, oauth2.S256ChallengeOption(verifier))
case types.PKCEMethodPlain:
// oauth2 does not have a plain challenge option, so we add it manually
extras = append(extras, oauth2.SetAuthURLParam("code_challenge_method", "plain"), oauth2.SetAuthURLParam("code_challenge", verifier))
default:
// An unknown method must not silently emit no challenge: a
// verifier was generated and is sent at token exchange, so a
// missing challenge degrades to no-PKCE without anyone noticing.
httpError(writer, NewHTTPError(http.StatusInternalServerError, "internal server error", fmt.Errorf("%w: %q", errInvalidPKCEMethod, a.cfg.PKCE.Method)))
return
}
}
// Add any extra parameters from configuration
for k, v := range a.cfg.ExtraParams {
extras = append(extras, oauth2.SetAuthURLParam(k, v))
}
extras = append(extras, oidc.Nonce(nonce))
// Cache the registration info
a.authCache.Add(state, registrationInfo)
authURL := a.oauth2Config.AuthCodeURL(state, extras...)
log.Debug().Caller().Msgf("redirecting to %s for authentication", authURL)
http.Redirect(writer, req, authURL, http.StatusFound)View on GitHub (pinned to 565fd254d0)
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
Example fix
# before
oidc:
pkce:
method: s256
# after
oidc:
pkce:
method: S256 Defensive patterns
Strategy: validation
Validate before calling
switch cfg.PKCE.Method {
case types.PKCEMethodS256, types.PKCEMethodPlain:
default:
return fmt.Errorf("invalid oidc.pkce.method %q: must be S256 or plain", cfg.PKCE.Method)
} Prevention
- 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
When it happens
Trigger: Configuring oidc.pkce.method to a value outside {S256, plain} (case-sensitive), or leaving an experimental/typo value like 's256' or 'SHA256'.
Common situations: 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.
Understand the failure class
- HTTP status errors: handling 4xx and 5xx responses — how to handle 4xx and 5xx responses properly.
Related errors
- pkce.method must be either 'plain' or 'S256'
- oidc.client_id is required when oidc.issuer is set
- oidc.client_secret or oidc.client_secret_path is required wh
- errOIDCIssuerInvalid
- auth request rejected
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/4b79d1fdeb329062.
Report an issue: GitHub.