422 Invalid form authenticity token error on issue pages
(Redmine #17588).
When an issue page is left open in a background tab and later restored — or the
session simply expires during a long editing session — the CSRF token embedded in
the form goes stale. Clicking Edit and submitting then fails with
422 Invalid form authenticity token, and the work typed into the form is lost.
- Injects a small script and two meta tags into every page
<head>through a view hook (view_layouts_base_html_head), so no core templates are modified. - Exposes a lightweight endpoint that returns a fresh token and the login status:
GET /csrf_token→{ "token": "…", "logged_in": true }(JSON), or{ "logged_in": false }when the session is gone.- The endpoint skips the login / password-change / 2FA before-actions so it stays reachable when a token is stale.
- It also skips
session_expiration, which would callreset_sessionon a stale session. Redmine keeps the session in the cookie, so that would log the user out of every open tab — including one where they just logged back in. The staleness is reported inlogged_ininstead, and a logged out request is answered without writing a session cookie at all.
- Guards the issue page at two points:
- Edit button click — an early session check before the user starts typing.
- Form submit — a last-moment token refresh that catches sessions which
expired mid-edit. The cancelled submit is then replayed as a real
submitevent, so Redmine's own handlers (double submit guard, unsaved changes tracking) still run, and the clicked button's name/value —continue,follow— is carried over in a hidden field.
- If the session is actually logged out, a translated warning is shown instead of silently failing, so the user can log back in without losing context.
- Localized in 8 languages: English, Spanish, French, Italian, Korean, Russian, Turkish and Chinese.
# from the Redmine root
cp -r redmine_csrf_refresh plugins/ # or clone into plugins/There is no database migration. Fully restart Redmine so the new plugin is picked up (hook registration, locales and assets load at boot):
sudo systemctl restart puma # systemd + Puma (use your service name)
# Passenger: passenger-config restart-app $(pwd)
# development: restart bin/rails server (or: bin/rails restart)- No configuration and no database changes — install and restart.
- Relies only on standard Redmine view hooks, the plugin asset pipeline, and the
showAndScrollTohelper already used on issue pages.
redmine_csrf_refresh is licensed under GPL-3.0-only. See LICENSE file for details.