| gradle/wrapper | Loading last commit info... | |
| src | ||
| .gitignore | ||
| LICENSE.txt | ||
| README.md | ||
| bitbucket-pipelines.yml | ||
| build.gradle.kts | ||
| gradle.properties | ||
| gradlew | ||
| gradlew.bat | ||
| settings.gradle |
CSRF protection for Ktor
See https://www.owasp.org/index.php/Cross-Site_Request_Forgery_(CSRF)_Prevention_Cheat_Sheet and https://seclab.stanford.edu/websec/csrf/csrf.pdf for details on what CSRF is and available countermeasures.
Usage
Artifacts are available via maven central, shown here in Gradle style:
implementation("org.mpierce.ktor.csrf", "ktor-csrf", "<LATEST_VERSION>")
Your configuration may vary, but here's a solid starting point: checking that the Origin header is your actual site,
and enforcing that a custom header is present (which your front-end JavaScript/etc is presumably sending).
install(CsrfProtection) {
// protect all routes by default
applyToAllRoutes()
validate(OriginMatchesKnownHost("https", "my-service.com"))
validate(HeaderPresent("X-Some-Custom-Header-Your-Frontend-Sends"))
}
// ... configure routing ...
routing {
get("/protectedByDefault") {
// ...
}
// exclude these routes from csrf protection
noCsrfProtection {
get("/unprotected") {
// ...
}
// ... except for this one, which should be protected (overriding the enclosing noCsrfProtection)
csrfProtection {
get("/protected")
}
}
}
Details
A first step is to validate that the origin of the request (represented by the Origin header) matches
the host that your service is deployed at, e.g. https://my-service.com. See OriginMatchesKnownHost for an
out-of-the-box implementation.
If configuring the service to know its deployment host ahead of time is infeasible, another approach would
be to inspect the Host or X-Forwarded-Host headers and ensure that they match Origin. See
OriginMatchesHostHeader.
Referer (sic) is another header that can sometimes contain origin info, but with plain HTTP (not HTTPS) it is
sometimes stripped out by proxies, and the ReferrerPolicy response header can configure browsers to not send a
Referer, so we just stick with Origin.
Another layer of defense is to require a custom header to be sent. This is feasible when the intended client is a
purely AJAX-driven or otherwise has tight control over requests. Typical CSRF request would not have the
opportunity to set any headers beyond what the browser provides by default. See HeaderIsPresent for an implementation
of this.