-
Notifications
You must be signed in to change notification settings - Fork 23
Getting started
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
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)
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
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
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
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)