refrakt/csrf
Cross-site request forgery (CSRF) protection.
A CSRF attack is a page on another site submitting a form to your app,
riding on the visitor’s session cookie. protect stops it by asking
the browser where an unsafe request (POST, PUT, PATCH, DELETE) came
from, and answering 403 Forbidden when it came from another site:
Sec-Fetch-Site, sent by all current browsers over HTTPS and to localhost.same-originandnone(typed or bookmarked by the user) pass;same-siteandcross-siteare rejected.- Otherwise
Origin, compared with theHostheader. - Otherwise
Referer, compared with theHostheader. - A request with none of these did not come from a browser page
(curl, another server, a mobile app) or came from a very old
browser. It is let through with its
cookieheader removed, so it cannot act as the signed-in user.
Browsers set these headers themselves and pages cannot forge them, so
no token has to be threaded through your forms. This is the approach
of Go’s http.CrossOriginProtection and Wisp’s
csrf_known_header_protection, with Sec-Fetch-Site support and a
403 status. Keep GET and HEAD handlers free of side effects: they
are never checked.
Behind a reverse proxy, forward the original Host header (nginx:
proxy_set_header Host $host;). Browsers on plain HTTP do not send
Sec-Fetch-Site, and the Origin check needs the host the browser
saw.
Example
fn middleware(req: Request, next: fn(Request) -> Response) -> Response {
let req = wisp.method_override(req)
use <- wisp.log_request(req)
use req <- csrf.protect(req)
next(req)
}
Types
What check decided about a request.
pub type Verdict {
Allowed
AllowedWithoutCookies
Rejected(reason: String)
}
Constructors
-
AllowedA safe method, or an unsafe one sent by a page on this site.
-
AllowedWithoutCookiesAn unsafe request without any browser provenance headers. It may go ahead, but without cookies.
-
Rejected(reason: String)An unsafe request sent by a page on another site.
Values
pub fn check(req: request.Request(body)) -> Verdict
Decide whether a request may go ahead. protect acts on this verdict;
use check directly to build your own response.
pub fn protect(
req: request.Request(body),
next: fn(request.Request(body)) -> response.Response(wisp.Body),
) -> response.Response(wisp.Body)
Reject cross-site unsafe requests with 403 Forbidden, and strip the
cookies from unsafe requests that carry no provenance headers.