Pac4jModuleRegistry: no hook for matchers/authorizers; non-rotating CSRF token expires after 4 h and is never renewed #62
Labels
No labels
bug
documentation
duplicate
enhancement
good first issue
help wanted
invalid
question
wontfix
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
iterative-works/support#62
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found while enabling pac4j CSRF in a downstream HTMX app (MEDECA-348). There are two independent items. Each can be fixed without the other.
Line numbers refer to
mainatb6d4157c.Pac4jCsrfSupport.scalais unchanged betweenv0.1.23,v0.1.24andmain.Item 1:
Pac4jModuleRegistry.wrapModulegives no way to set matchers or authorizersPac4jHttpSecurity.secureacceptsauthorizersandmatchers(server/http/src/main/scala/works/iterative/server/http/impl/pac4j/Pac4jHttpSecurity.scala:116-118, also:76-77).Pac4jModuleRegistry.wrapModulecallspac4jSecurity.secure(clients = clients)only (.../pac4j/Pac4jModuleRegistry.scala:24). The only overridable hook isdef clients: Option[String] = None(:15).A project that needs an explicit security policy must copy the whole
wrapModulebody (:17-35) into an override to change one call. There are valid reasons for an explicit policy:csrfToken/csrfCheckwithout taking pac4j's defaultsecurityHeadersmatchersDirectClient, because pac4j's default addscsrfCheckonly for anIndirectClientThe copy then drifts from the parent without notice. MEDECA has carried such a copy since its CSRF was turned off.
Proposal: add
def matchers: Option[String] = Noneanddef authorizers: Option[String] = Nonebesideclients, and pass them through at:24. TheNonedefaults keep today's behaviour. Add one sentence todocs/CSRF_GUIDE.mdthat names the hooks.Item 2:
installNonRotatingMatcheryields a token that stops working 4 hours after the session's first secured requestPac4jCsrfSupport.installNonRotatingMatchercreates a stockDefaultCsrfTokenGeneratorand calls onlysetRotateTokens(false)(.../pac4j/Pac4jCsrfSupport.scala:68-79).In pac4j-core 6.3.2, the generator writes
CSRF_TOKEN_EXPIRATION_DATEonly insideif (optCurrentToken.isEmpty() || rotateTokens)(DefaultCsrfTokenGenerator.java:44-51). The TTL is4*60*60(:26). With rotation off, the expiry is set once and never extended.CsrfAuthorizerrejects a token whose expiration date has passed.The result: every POST/PUT/PATCH/DELETE in a session returns 403, starting 4 h after the first request through
secure(...). This continues until the session is destroyed. If session data survives re-login, a fresh login does not recover it. An HTMX client does not swap a 4xx response, so the user sees a silent no-op.The documentation states the opposite.
docs/CSRF_GUIDE.md:77says "The token is stable for the session's lifetime." The helper's scaladoc (Pac4jCsrfSupport.scala:55-66) does not mention expiry. Every consumer that follows the guide recipe (CSRF_GUIDE.md:13,:129) is affected.Proposal:
installNonRotatingMatcherinstalls aCsrfTokenGeneratorthat keeps the existing token and refreshesCSRF_TOKEN_EXPIRATION_DATEon each matched request (sliding expiry). Add an optional TTL parameter. Add a test that moves the clock past the TTL with requests in between and expects the POST to pass. CorrectCSRF_GUIDE.md:77.MEDECA will carry such a generator as a project class until this ships, and will then delete it.