xkollar

Heroku and HTTPS

Posted on 2017-03-15 by xkollar in Haskell.

Heroku is a Platform as a Service provider we have used in work for prototyping some small services. What bugged me though was that even though they provide you with free HTTPS certificate for your *.herokuapp.com site, they do not provide simple way to enforce HTTPS.

So I have wrote simple wai Middleware, which will take care of redirecting users to secured version of your site and also set HTTP_Strict_Transport_Security.

herokuHttps :: Middleware
herokuHttps = strictTransportPolicy . herokuHttpsRedirect

isHerokuHttps :: Request -> Bool
isHerokuHttps = (Just "https" ==) . fmap CI.mk
    . lookup "X-Forwarded-Proto" . requestHeaders

strictTransportPolicy :: Middleware
strictTransportPolicy = ifRequest isHerokuHttps . modifyResponse
    $ mapResponseHeaders ((header, value):)
  where
    header = "Strict-Transport-Security"
    value = "max-age=31536000; includeSubDomains"

herokuHttpsRedirect :: Middleware
#if MIN_VERSION_wai(3,0,0)
herokuHttpsRedirect app r respond
#else
herokuHttpsRedirect app r
#endif
    | isHerokuHttps r = runApp
    | Just host <- hostHeader r = res . redir
        $ "https://" <> host <> rawPathInfo r <> rawQueryString r
    | otherwise = runApp
  where

    -- Heroku does not use "X-Forwarded-Host", as it simply preserves "Host"
    hostHeader :: Request -> Maybe ByteString
    hostHeader = lookup "Host" . requestHeaders

    redir :: ByteString -> Response
    redir url = responseLBS HTTP.status301 [("Location", url)] ""

#if MIN_VERSION_wai(3,0,0)
    res = respond
    runApp = app r respond
#else
    res = return
    runApp = app r
#endif

Many relevant Haskell libraries use wai (wai reverse dependencies so you can use it for example with your servant-based service.

PS: You really do not need this should you have proxy configuration under your control as all major reverse proxies can do this for you.