ory/hydra · error
Invalid action
Error message
Invalid action
What it means
A handler-generated error: loginPOST received an "action" form value that is neither "accept" nor "deny", so the switch's default case rejects the request with 400 "Invalid action". This is local input validation in the example app, not a Hydra error.
Source
Thrown at cmd/cmd_perform_authorization_code.go:445
}).Execute()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer res.Body.Close() //nolint:errcheck
http.Redirect(w, r, req.RedirectTo, http.StatusFound)
case "deny":
req, res, err := rt.cl.OAuth2API.RejectOAuth2LoginRequest(r.Context()).LoginChallenge(r.FormValue("ls")).Execute()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer res.Body.Close() //nolint:errcheck
http.Redirect(w, r, req.RedirectTo, http.StatusFound)
default:
http.Error(w, "Invalid action", http.StatusBadRequest)
}
}
func (rt *router) consentGET(w http.ResponseWriter, r *http.Request) {
req, raw, err := rt.cl.OAuth2API.GetOAuth2ConsentRequest(r.Context()).
ConsentChallenge(r.URL.Query().Get("consent_challenge")).
Execute()
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
defer raw.Body.Close() //nolint:errcheck
if rt.skip && req.GetSkip() {
req, res, err := rt.cl.OAuth2API.AcceptOAuth2ConsentRequest(r.Context()).
ConsentChallenge(req.Challenge).
AcceptOAuth2ConsentRequest(openapi.AcceptOAuth2ConsentRequest{
GrantScope: req.GetRequestedScope(),View on GitHub (pinned to 4174065ffb)
Solutions
- Include action=accept or action=deny in the POSTed form
- Check the login template still renders the submit buttons with name="action"
- Handle unknown actions gracefully by redirecting to a fresh flow instead of a bare 400
- Case-normalize the action value before the switch
Example fix
// before
default:
http.Error(w, "Invalid action", http.StatusBadRequest)
// after
default:
log.Printf("login: unknown action %q", r.FormValue("action"))
http.Error(w, "invalid action; must be accept or deny", http.StatusBadRequest) Defensive patterns
Strategy: validation
Validate before calling
func knownLoginAction(a string) bool {
switch a {
case "accept", "deny":
return true
}
return false
}
// before the switch:
if !knownLoginAction(r.FormValue("action")) {
http.Error(w, "invalid action; must be accept or deny", http.StatusBadRequest)
return
} Type guard
func isLoginAction(s string) bool {
return s == "accept" || s == "deny"
} Prevention
- Keep the action value a single canonical string in forms and scripts
- Use buttons with name="action" and fixed value attributes
- Case-normalize input before dispatch
- Reject unknown actions early with a clear message
When it happens
Trigger: Posting to /login without an action field, with a misspelled action (e.g. "Accept"), or via curl/automation that omits the button value the HTML form normally supplies.
Common situations: Custom clients or scripts hitting the login endpoint directly without the action field; HTML form button name/value changed or removed in a template edit; proxies stripping form fields.
Related errors
- errKeyNotFound
- device_challenge is required
- user_code is required
- Failed to accept user code request: %s
- issuer URL must be set unless development mode is enabled
AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03).
Data as JSON: /api/errors/190038ef4c4f7e19.
Report an issue: GitHub.