@hackage biscuit-haskell0.2.0.0

Library support for the Biscuit security token

biscuit-haskell CI-badge Hackage

Main library for biscuit tokens support, providing minting and signature verification of biscuit tokens, as well as a datalog engine allowing to compute the validity of a token in a given context.

Supported biscuit versions

The core library supports v2 biscuits (both open and sealed).

How to use this library

This library was designed with the use of QuasiQuotes in mind.

A minimal example is provided in the library itself, and the package documentation contains comprehensive examples and explanations for all the library features.

Familiarity with biscuit tokens will make the examples easier to follow. Reading the biscuit presentation and the biscuit tutorial is advised.

Checking a biscuit token

To make sure a biscuit token is valid, two checks have to take place:

  • a signature check with a public key, making sure the token is authentic
  • a datalog check making sure the token is authorized for the given context
-- public keys are typically serialized as hex-encoded strings.
-- In most cases they will be read from a config file or an environment
-- variable
publicKey' :: PublicKey
publicKey' = case parsePublicKeyHex "todo" of
  Nothing -> error "Error parsing public key"
  Just k  -> k

-- this function takes a base64-encoded biscuit in a bytestring, parses it,
-- checks it signature and its validity. Here the provided context is just
-- the current time (useful for TTL checks). In most cases, the provided context
-- will carry a permissions check for the endpoint being accessed.
verification :: ByteString -> IO Bool
verification serialized = do
  now <- getCurrentTime
  -- biscuits are typically serialized as base64 bytestrings. The publicKey is needed
  -- to check the biscuit integrity before completely deserializing it
  biscuit <- either (fail . show) pure $ parseB64 publicKey' serialized
  -- the verifier can carry facts (like here), but also checks or policies.
  -- verifiers are defined inline, directly in datalog, through the `verifier`
  -- quasiquoter. datalog parsing and validation happens at compile time, but
  -- can still reference haskell variables.
  let authorizer' = [authorizer|time(${now});
                                allow if true;
                               |]
  -- `authorizeBiscuit` only works on valid biscuits, and runs the datalog verifications
  -- ensuring the biscuit is authorized in a given context
  result <- authorizeBiscuit biscuit authorizer'
  case result of
    Left e  -> print e $> False
    Right _ -> pure True

Creating (and attenuating) biscuit tokens

Biscuit tokens are created from a secret key, and can be attenuated without it.

-- secret keys are typically serialized as hex-encoded strings.
-- In most cases they will be read from a config file or an environment
-- variable (env vars or another secret management system are favored,
-- since the secret key is sensitive information).
-- A random secret key can be generated with `generateSecretKey`
secretKey' :: SecretKey
secretKey' = case parseSecretPrivateKeyHex "todo" of
  Nothing -> error "Error parsing secret key"
  Just k  -> k

creation :: IO ByteString
creation = do
  -- biscuit tokens carry an authority block, which contents are guaranteed by the
  -- secret key.
  -- Blocks are defined inline, directly in datalog, through the `block`
  -- quasiquoter. datalog parsing and validation happens at compile time, but
  -- can still reference haskell variables.
  let authority = [block|
       // toto
       resource("file1");
       |]
  biscuit <- mkBiscuit secretKey authority
  -- biscuits can be attenuated with blocks. blocks are not guaranteed by the secret key and
  -- should only restrict the token use. This property is guaranteed by the datalog evaluation:
  -- facts and rules declared in a block cannot interact with previous blocks.
  -- Here, the block only adds a TTL check.
  let block1 = [block|check if time($time), $time < 2021-05-08T00:00:00Z;|]
  -- `addBlock` only takes a block and a biscuit, the secret key is not needed:
  -- any biscuit can be attenuated by its holder.
  newBiscuit <- addBlock block1 biscuit
  pure $ serializeB64 newBiscuit