Skip to content

Getting started

Al Zohali edited this page May 12, 2017 · 2 revisions

Session data

To store and manipulate data in our cookies we need to define a datatype. We will use it to identify users and verify their authenticity:

data Account = Account
  { accUid       :: Int
  , _accUsername :: String
  , _accPassword :: String
  } deriving (Show, Eq, Generic)

instance Serialize Account

Instance of Data.Serialize.Serialize is required as we will transform it into binary form and vice versa.

Now we are going to say, that we will use it as a payload in our cookies:

type instance AuthCookieData = Account

Endpoints

Every endpoint that reads or modifies cookies should be wrapped in Cookied type. Every endpoint that is protected by the authentication should be prepended by AuthProtect "cookie-auth" type. A protected endpoint indirectly reads and modifies cookies, so such endpoint should always be wrapped in Cookied.

For example:

ExampleAPI = ...
  :<|> "login" :> ReqBody '[FormUrlEncoded] LoginForm :> Post '[HTML] (Cookied Markup)
  :<|> "logout" :> Get '[HTML] (Cookied Markup)
  :<|> "private" :> AuthProtect "cookie-auth" :> Get '[HTML] (Cookied Markup)

Case for servant < 0.9

Every endpoint that modifies cookies should be specified with Set-Cookie header. Read-only endpoints do not require any changes. Protected endpoints do not modify cookies, so again, no changes.

ExampleAPI = ...
  :<|> "login"
       :> ReqBody '[FormUrlEncoded] LoginForm
       :> Post '[HTML] (Headers '[Header "Set-Cookie" EncryptedSession] Markup)
  :<|> "logout"
       :> Get '[HTML] (Headers '[Header "Set-Cookie" EncryptedSession] Markup)
  :<|> "private" :> AuthProtect "cookie-auth" :> Get '[HTML] Markup

Handlers

To set cookies and to read them we use functions addSession and cookied respectively.

Both functions have the same first three arguments (they will be explained later) and it's convenient to define shorter versions in where clause, i.e.:

addSession' = addSession settings rs sks
cookied' = cookied settings rs sks

addSession' takes two arguments -- value of type AuthCookieData (that will go into cookies) and what will be returned to a client.

cookied' is a wrapper, that takes handler with extra argument of type AuthCookieData and returns a handler. Behind the scenes it might change cookies metadata.

They can be used like this:

serveLogin loginData = if check loginData
  then addSession' (Account 1 "admin" "password") welcomePage
  else throwError err401

serveProfile = cookied' serveProfile'
serveProfile' (Account i u _) = return $ profilePage i u

Case for servant < 0.9

For earlier versions of servant there is no cookied function. Instead addSession can be used (if changing the cookies is required) or the following construction (if the handler doesn't change the cookies):

  serveProfile = return . serveProfile . wmData

Putting all together

We still need to provide some additional parameters before we will be able to define the application. To avoid bloating this section, let's use the simplest values:

let settings = (def :: AuthCookieSettings) {acsCookieFlags = ["HttpOnly"]}
let sks = mkPersistentServerKey "0123456789abcdef"
rs <- mkRandomSource drgNew 1000

let app = serveWithContext
  (Proxy :: Proxy ExampleAPI)
  ((defaultAuthHandler settings sks) :. EmptyContext)
  (server settings rs sks)
Clone this wiki locally