servant-hmac-auth: Servant authentication with HMAC

[ cryptography, library, mit, web ] [ Propose Tags ] [ Report a vulnerability ]

Servant authentication with HMAC. See README.md for usage example.


[Skip to Readme]

Downloads

Maintainer's Corner

Package maintainers

For package maintainers and hackage trustees

Candidates

Versions [RSS] 0.0.0, 0.1.1, 0.1.2, 0.1.3, 0.1.4, 0.1.5, 0.1.6, 0.1.7, 0.1.8
Change log CHANGELOG.md
Dependencies base (>=4.11.1.0 && <4.21), base64-bytestring (>=1.0 && <=2), bytestring (>=0.10 && <0.13), case-insensitive (>=1.2 && <1.3), containers (>=0.5.7 && <0.8), cryptonite (>=0.25 && <0.31), http-client (>=0.6.4 && <0.8), http-types (>=0.12 && <0.13), memory (>=0.15 && <0.19), mtl (>=2.2.2 && <2.4), servant (>=0.19 && <0.21), servant-client (>=0.19 && <0.21), servant-client-core (>=0.19 && <0.21), servant-server (>=0.19 && <0.21), transformers (>=0.5 && <0.7), wai (>=3.2.2.1 && <3.3) [details]
Tested with ghc ==9.0.2, ghc ==9.2.8, ghc ==9.4.8, ghc ==9.6.6, ghc ==9.8.2, ghc ==9.10.1
License MIT
Copyright 2018 Holmusk
Author Holmusk
Maintainer tech@holmusk.com
Category Web, Cryptography
Home page https://github.com/holmusk/servant-hmac-auth
Bug tracker https://github.com/holmusk/servant-hmac-auth/issues
Source repo head: git clone https://github.com/holmusk/servant-hmac-auth.git
Uploaded by HolmuskTechTeam at 2024-10-31T05:57:34Z
Distributions NixOS:0.1.6
Downloads 1144 total (20 in the last 30 days)
Rating (no votes yet) [estimated by Bayesian average]
Your Rating
  • λ
  • λ
  • λ
Status Docs available [build log]
Last success reported on 2024-10-31 [all 1 reports]

Readme for servant-hmac-auth-0.1.7

[back to package description]

servant-hmac-auth

Hackage MIT license Stackage Lts Stackage Nightly

Servant authentication with HMAC

Example

In this section, we will introduce the client-server example. To run it locally you can:

$ cabal new-build
$ cabal new-exec readme

So,it will run this on your machine.

Setting up

Since this tutorial is written using Literate Haskell, first, let's write all necessary pragmas and imports.

{-# LANGUAGE DataKinds                  #-}
{-# LANGUAGE DeriveGeneric              #-}
{-# LANGUAGE GeneralizedNewtypeDeriving #-}
{-# LANGUAGE OverloadedStrings          #-}
{-# LANGUAGE TypeApplications           #-}
{-# LANGUAGE TypeOperators              #-}

import Control.Concurrent (forkIO, threadDelay)
import Data.Aeson (FromJSON, ToJSON)
import Data.Proxy (Proxy (..))
import GHC.Generics (Generic)
import Network.HTTP.Client (defaultManagerSettings, newManager)
import Network.Wai.Handler.Warp (run)
import Servant.API ((:>), Get, JSON)
import Servant.Client (BaseUrl (..), Scheme (..), ClientError, mkClientEnv)
import Servant.Server (Application, Server, serveWithContext)

import Servant.Auth.Hmac (HmacAuth, HmacClientM, SecretKey (..), defaultHmacSettings,
                          hmacAuthServerContext, hmacClient, runHmacClient, signSHA256)

Server

Let's define our TheAnswer data type with the necessary instances for it.

newtype TheAnswer = TheAnswer Int
    deriving (Show, Generic, FromJSON, ToJSON)

getTheAnswer :: TheAnswer
getTheAnswer = TheAnswer 42

Now, let's introduce a very simple protected endpoint. The value of TheAnswer data type will be the value that our API endpoint returns. It our case we want it to return the number 42 for all signed requests.

type TheAnswerToEverythingUnprotectedAPI = "answer" :> Get '[JSON] TheAnswer
type TheAnswerToEverythingAPI = HmacAuth :> TheAnswerToEverythingUnprotectedAPI

As you can see this endpoint is protected by HmacAuth.

And now our server:

server42 :: Server TheAnswerToEverythingAPI
server42 = \_ -> pure getTheAnswer

Now we can turn server into an actual webserver:

topSecret :: SecretKey
topSecret = SecretKey "top-secret"

app42 :: Application
app42 = serveWithContext
    (Proxy @TheAnswerToEverythingAPI)
    (hmacAuthServerContext signSHA256 topSecret)
    server42

Client

Now let's implement client that queries our server and signs every request automatically.

client42 :: HmacClientM TheAnswer
client42 = hmacClient @TheAnswerToEverythingUnprotectedAPI

Now we need to write function that runs our client:

runClient :: SecretKey -> HmacClientM a -> IO (Either ClientError a)
runClient sk client = do
    manager <- newManager defaultManagerSettings
    let env = mkClientEnv manager $ BaseUrl Http "localhost" 8080 ""
    runHmacClient (defaultHmacSettings sk) env client

Main

And we're able to run our server in separate thread and perform two quiries:

  • Properly signed
  • Signed with different key
main :: IO ()
main = do
    _ <- forkIO $ run 8080 app42

    print =<< runClient topSecret client42
    print =<< runClient (SecretKey "wrong!") client42

    threadDelay $ 10 ^ (6 :: Int)