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

  1. Include action=accept or action=deny in the POSTed form
  2. Check the login template still renders the submit buttons with name="action"
  3. Handle unknown actions gracefully by redirecting to a fresh flow instead of a bare 400
  4. 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

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


AI-assisted analysis of ory/hydra@4174065ffb (2026-09-03). Data as JSON: /api/errors/190038ef4c4f7e19. Report an issue: GitHub.