Chapter 2 - Supporting Libraries and Authorization

By John Lenz. 2016.

The design of the full-stack Haskell website has two supporting libraries. First, there is a library which is shared between the client and server and contains the domain datatypes and network API. Second, there is a library which contains the database and authentication operations used by the server.

domain-data library

The domain-data library contains the types shared between the client and server, business logic, and some utility functions that transform this data. Since this library is shared between GHCJS and GHC, I keep the dependencies small (for example, I use microlens instead of the full lens library).

In addition, this library depends on servant and contains the network API definitions. Unlike many of the servant examples, I like to give each route its own name with the full path and then group the routes by similar functionality before combining them together into the final full API type.

type GetTeamRoute = "team" :> Capture "teamkey" TeamKey :> Get '[JSON] Team
type CreateTeamRoute = "teams" :> ReqBody '[JSON] Team :> Post '[JSON] ()
type UpdateTeamRoute = "team" :> Capture "teamkey" TeamKey :> ReqBody '[JSON] Team :> Put '[JSON] ()
type GetAllTeamsRoute = "teams" :> Get '[JSON] (Vector Team)

type TeamAPI = GetTeamRoute :<|> CreateTeamRoute :<|> UpdateTeamRoute :<|> GetAllTeamsRoute


type MyAPI = TeamAPI :<|> UserAPI :<|> ...


I use JSON Web Tokens for authentication. JavaScript is required for the user interface which allows JavaScript to easily manage the web token using local storage. A cookie-based authentication scheme introduces complexity with cross-site request forgery attack prevention and complicates the server code.

The domain-data library contains the types and routes for authentication, while the actual authentication occurs inside the server code, not in domain-data. Servant has some support for authentication and there is now servant-auth-token but I am not using it (mainly because servant-auth-token came out after I had already implemented the authentication). First, I represent users via a UserCredentials structure and wrap that up into a Token structure to capture all data stored in the JSON web token.

-- | User credentials extracted from the JWT token
data UserCredentials = UserCredentials {
    _credId :: UserId
  , _credEmail :: Text
  , _credEmailVerified :: Bool
} deriving (Show, Generic)

makeLenses ''UserCredentials

-- | There are two kinds of tokens.  When reseting the password, we send
-- a token via email.  When a user logs in, we send a token to the browser.
data TokenAudience = TokenSentViaEmail | TokenSentToBrowser
    deriving (Show, Eq, Generic)

-- | The contents of the Jwt token is an encoding of this structure.
data Token = Token {
    _tokenCreds :: UserCredentials
  , _issuedP :: POSIXTime
  , _expiredP :: POSIXTime
  , _jwtAudience :: TokenAudience
} deriving (Show, Eq, Generic)

makeLenses ''Token

instance Aeson.FromJSON Token where
    parseJSON = Aeson.withObject "web token" $ \o -> do
        aud :: Text <- o .: "aud"
        aud' <- case aud of
                    "mycompany:email" -> pure TokenSentViaEmail
                    "mycompany:web" -> pure TokenSentToBrowser
                    _ -> fail "Invalid audience for token"
        Token <$> (UserCredentials <$> o .: "sub" <*> o .: "email" <*> o .: "email_verified")
              <*> (fromInteger <$> o .: "iat")
              <*> (fromInteger <$> o .: "exp")
              <*> pure aud'

instance Aeson.ToJSON Token where
    toJSON (Token ucreds i e a) = Aeson.object
        [ "sub" .= (ucreds^.credId), "email" .= (ucreds^.credEmail), "email_verified" .= (ucreds^.credEmailVerified)
        , "iat" .= (round i :: Int64)
        , "exp" .= (round e :: Int64)
        , "aud" .= case a of
                    TokenSentViaEmail -> asText "mycompany:email"
                    TokenSentToBrowser -> asText "mycompany:web"

The token is sent via the Authorization header. I look for the header on all routes, even those for which authentication is not required. See the server design for details on how the header is handled.

newtype UnverifiedJwtToken = UnverifiedJwtToken Text
  deriving (Show, FromHttpApiData)
type JwtAuthHeader = Header "authorization" UnverifiedJwtToken
type MyAPIWithAuth = JwtAuthHeader :> MyAPI

Finally, the domain-data library has a small test suite built with hspec.


The datastorage library contains the database actions and authorization code (deciding which users can access which resource). The reason for separating this into its own library is unit testing. For the most part, Haskell code needs some but does not need a lot of unit testing (the domain-data unit test is quite small). Database and authorization code is the exception. I am using persistent, but the design works with any of Haskell's database and datastorage tools.

One key design question is if the same types from the network API in domain-data are also used for the database. Some types like User must differ between the database and the network API because in the database you need to store the hashed password and so on. For other types, I decide on a case-by-case basis. If no processing happens on the server and the data is just passed back and forth to the client, I reuse the type from the design-data library. If some processing occurs on the server or if some operation operates on only a subset of the data, I create a new data type in the datastorage library and write conversion functions between the type in domain-data used for the network API and the type in datastorage used for the database.

The datastorage library then consists of functions which first perform authorization and then access the database; a UserCredentials structure is passed to each function. For example, using persistent,

isMemberOfTeam :: UserCredentials -> Team -> Bool
user `isMemberOfTeam` team = {- implementation here -}

loadTeam :: UserCredentials -> TeamKey -> SqlPersistM Team
loadTeam ucreds teamkey = do
    mteam <- get teamkey
    case mteam of
        Nothing -> throwM DocumentNotFound
        Just t | ucreds `isMemberOfTeam` t -> return t
               | otherwise -> throwM Unauthorized

Similar functions exist for updating the team (which checks if the user is a team administrator), deleting teams, querying teams, and so on.

Test Suite

The datastorage library contains a large test suite in hspec. To easily test all combinations of users, permissions, and operations, the test suite is built around a free monad. The free monad is built with the free package, and contains operations for creating the various resources and then expectations of success or failure to access those resources. One advantage of using a DSL is that it can also be re-used to test the server via HTTP requests (explained in a later chapter).

data TestDSL next = CreateUser (UserCredentials -> next)
                  | CreateTeam (TeamKey -> next)
                  | AddUserToTeam TeamKey UserCredentials MembershipLevel next
                  | ExpectSucceedViewTeam TeamKey UserCredentials next
                  | ExpectFailViewTeam TeamKey UserCredentials next
    deriving Functor

createUser :: Free TestDSL UserCredentials
createUser = liftF $ CreateUser id

createTeam :: Free TestDSL TeamKey
createTeam = liftF $ CreateTeam id

addUserToTeam :: TeamKey -> UserCredentials -> MembershipLevel -> Free TestDSL ()
addUserToTeam k u m = liftF $ AddUserToTeam k u m ()


The main advantage of using such a DSL is the tests can be written in the DSL and therefore focus on the operations and if they should succeed or fail, without getting too involved in the gory details. For example, the interpreter for CreateTeam randomly fills in the team name and then checks it inside ExpectSucceedViewTeam, but the tests written in the DSL avoid this detail. This provides a focus for the tests and makes them easier to audit to make sure the various permissions are being correctly tested.

To write the tests in the DSL, I use a writer monad to create a list of specs .

it :: String -> Free TestDSL () -> Writer [(String, Free TestDSL ())]
it n f = tell [(n, f)]

testDSL :: [(String, Free TestDSL ())]
testDSL = snd $ runWriter $ do
    it "allows a team administrator to access a team" $ do
        u <- createUser
        t <- createTeam
        addUserToTeam u t Administrator
        expectSucceedViewTeam u t
        expectSucceedEditTeam u t

    it "denies a non-team member from accessing a team" $ do
        u <- createUser
        t <- createTeam
        expectFailViewTeam u t


Since these tests are in a monad, I use mapM to iterate through various possible team membership levels and can therefore make sure users are correctly allowed or denied access to resources for all possibile membership levels.

For the DSL interpreter, I use a StateT transformer to keep the expected values of the various resources.

data TestState = TestState
   { _testTeams :: HashMap TeamKey Team
   , ....
makeLenses ''TestState

type TestT a = StateT TestState SqlPersistM a

runAction :: TestDSL (TestT ()) -> TestT ()
runAction (CreateTeam continue) = do
    team <- {- randomly create a team -}
    key <- lift $ insert team -- insert is from persistent
    (testTeams . at key) .= Just team
    continue key

runDSL :: Free TestDSL () -> SqlPersistM ()
runDSL free = flip evalStateT (TestState mempty) $
    iterM runAction free

Finally, I turn the examples defined in the DSL into an actual hspec spec.

spec :: Test.Hspec.SpecWith SqlBackend
spec = Test.Hspec.describe "DSL spec" $
    forM_ testDSL $ \(name, free) -> name $ \backend ->
            runSqlPersistM (runDSL free) backend