Skip to content

soywiz-archive/marshallpierce-ktor-csrf

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Download

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 jcenter:

repositories {
  jcenter()
}

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) {
    validate(OriginMatchesKnownHost("https", "my-service.com"))
    validate(HeaderPresent("X-Some-Custom-Header-Your-Frontend-Sends"))
}

// ... configure routing ... 

routing {
    csrfProtection { 
        get("/protected") { 
            // ...
        }
    }
    get("/unprotected") {
        // ...
    }
}

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.