csrf #
Signed OAuth state parameters, against cross-site request forgery.
An OAuth client sends the user to the provider with a state parameter and
checks, when the provider redirects back, that the same value returns. If the
value is not bound to the client, an attacker can start a login flow of their
own and send the victim the callback, logging the victim into the attacker's
account. csrf binds the state to a server secret: Csrf.sign_state appends a
dot and the hex HMAC-SHA256 tag of the state under a key derived from the secret
with HKDF (RFC 5869), and Csrf.verify_state checks the tag in constant time
and returns the original state. The tag proves only that this server minted the
state; the attacker's own flow gets a validly signed state too. The binding to
the victim's browser is the caller's: it also stores the state in a cookie on
the browser that started the flow and requires the callback's state to equal
that cookie, as auth does with its oauth_state cookie (RFC 6749, section
10.12). It refuses any signed state longer than 256 bytes before hashing it. The
tag has no expiry and no nonce, so a caller that needs either puts it inside the
state.
Install #
$ opam install csrf
If opam cannot find the package, add the overlay repository first:
$ opam repo add samoht https://tangled.org/gazagnaire.org/opam-overlay.git
$ opam update
$ opam install csrf
Usage #
Sign the state before the redirect, and verify it when the browser returns:
let signed = Csrf.sign_state ~secret:"server secret" "oauth-login"
let () =
match Csrf.verify_state ~secret:"server secret" signed with
| Some payload -> assert (payload = "oauth-login")
| None -> failwith "tampered state"
License #
ISC. See LICENSE.md.