-
Notifications
You must be signed in to change notification settings - Fork 23
Home
servant-auth-cookie adds support for authentication via cookies into servant framework. It was inspired by Michael Snoyman's library client-session and based on ideas of the paper "A Secure Cookie Protocol" by Alex Liu et al. Session data is stored in cookies in encrypted form, so the client is unable to read nor forge it.
Library comes with an example that uses the most of the API. It might help you to understand how to use the library.
To run the example enable flag build-examples and run executable example:
cabal configure -f build-example -f servant91
cabal run example
(Note: it's recommended to use >= 0.9.1.* versions of servant for more features will be enabled.)
This will launch local server at 8080 port. It's a simple
three/four-paged web site that will show the private page only if a
correct cookie is presented. For valid accounts see usersDB list in
example/AuthAPI.hs.
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 = AccountEvery 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 sksaddSession' 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 uFor 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 . wmDataWe 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)
((authHandler settings sks) :. EmptyContext)
(server settings rs sks)
TODO
TODO