Servant Auth and Elm

2017-06-06
haskellelm

servant-auth-and-elm-example is a simple project that shows you how to make an Elm application that can log in, log out and make authenticated requests to a servant back-end. servant-auth adds JSON Web Token authentication to servant. It supports authentication via API tokens or browser cookies. In this post I will highlight how to authentication works between servant, servant-auth and Elm. Hopefully that will be sufficient for you to follow the code in the git repository.

servant and servant-auth

The back-end code follows the example in the servant-auth README.md closely. The code is all in one file Server.hs.

Protected API

The Protected API requires authentication to use. Any resource that requires authentication should be placed in this API.

The first route is just a simple random number generator for the front-end to call when the user is logged in. The front-end uses the second route to see if the current token in the browser’s cookies are valid. If they are then the browser gets the user data (excluding the password) and can display the user email. You can expand this to include any user you want to display to the currently logged in user. For simplicity the loggedin route is just returning a hard coded value. This can easily be expanded to a database system of your choice.

Unprotected API

The Unprotected API provides any resources that do not require authentication: login request, front-end files, home page, static resources, etc.

The login route returns two Set-Cookie values in the response header if the authentication is successful and returns no value in the body. The last route Raw will catch all other API calls. Servant route lookup is in order of declaration, from top to bottom. Raw with no sub-paths needs to be located at the end or it will catch routes that we do not want it to catch.

checkCreds handles authentication. Again, for simplicity we use hard coded values but we can easily add a database to handle user data.

staticFiles declares the lookup name we will use in Raw and the Template Haskell code $(embedFile ...) will load the file contents at compile time. index.html is where we store the compiled Elm code. The last piece of interest is ssIndices. This tells haskell to treat a root lookup / as /index.html.

Take a look at the cabal file and Setup.hs. I have added elm/src/Main.elm to data-dirs and some custom code so that when you run stack build it will automatically build the elm code and then build the servant project if there have been changes to either the Elm or the Haskell project.

Elm

The front-end for this example can do four things: check if the user is logged in upon loading the page, log in, log out and request a die roll from the server if the user is authenticated.

Native Code

As mentioned here. Each query to an authenticated requires the front-end to set X-XSRF-TOKEN in the header. In order to do this, we need to write native JavaScript code that is called by Elm. To do so, we must name the function for the user and project name in the repository key in elm-package.json. When you initiate a new elm project it will intially set as user and project.

The csrfCookie function gets XSRF-TOKEN from the cookies and sends it to Elm. deleteCookie sets the XSRF-TOKEN to an expired date to delete it and logout the user. Then it redirects the user to the root path. There is a small layer of code in CsrfCookie.elm to call these two functions from Elm.

Then there is a function sendWithCsrfToken using elm-http-builder to call csrfCookie and set X-XSRF-TOKEN for any http-builder request that you give it. Finally, here is an example of making a simple authenticated request.

update : Msg -> Model -> (Model, Cmd Msg)
update msg model =
  case msg of
    RollDieRequest ->
      let
        request = 
          HttpB.get "/die/roll" 
          |> HttpB.withExpect (Http.expectJson Json.Decode.int)
      in
        (model, sendWithCsrfToken RollDieResponse request)

Resources