Basic HTTP auth with Scotty

Published on

Not so long ago, I needed to write a web app to automate the recording of our Haskell podcast, Bananas and Lenses.

To build it, I chose a lightweight Haskell web framework called Scotty. There is another lightweight Haskell web framework called Spock. Both start with the letter S and are characters from Star Trek, and I have little hope ever being able to tell which is which by name. I can say though that I enjoyed working with the one I happened to pick.

So, anyway, I needed to ensure that only my co-hosts and I could access the app. In such a simple scenario, basic HTTP auth is enough. I did a quick google search for “scotty basic auth”, but all I found was this gist in which the headers are extracted by hand. Ugh.

Indeed, at the time of writing, Scotty itself does not seem to provide any shortcuts for basic auth. And yet the solution is simple and beautiful; you just need to step back to see it. Scotty is based on WAI, the Haskell web application interface, and doesn’t attempt to hide that fact. On the contrary, it conveniently exposes the function

middleware :: Middleware -> ScottyM ()

which “registers” a WAI wrapper that runs on every request. And sure enough, WAI (wai-extra) provides an HttpAuth module.

To put everything together, here’s a minimal password-protected Scotty application (works with Stackage lts-5.1).

{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Network.Wai.Middleware.HttpAuth
import Data.SecureMem -- for constant-time comparison
import Lucid -- for HTML generation

password :: SecureMem
password = secureMemFromByteString "An7aLasi" --

main :: IO ()
main = scotty 8000 $ do
  middleware $ basicAuth (\u p -> return $ u == "user" && secureMemFromByteString p == password)
    "Bananas and lenses recording"

  get "/" . html . renderText $ do
    html_ $ do
      head_ $ do
        title_ "Bananas and lenses recording"

      body_ $ h1_ "Hello world!"

Two security-related points:

  1. Data.SecureMem is used to perform constant-time comparison to avoid a timing attack.
  2. Ideally, the whole thing should be run over https (as the password is submitted in clear), but this is outside of the scope of this article.