wai-cryptocookie-0.0.1: Encrypted cookies for WAI
Safe HaskellNone
LanguageGHC2021

Wai.CryptoCookie

Description

This module exports tools for safely storing encrypted data on client-side cookies through Network.Wai.

This module is designed to be imported as follows:

import qualified Wai.CryptoCookie

One example of how to obtain a new Network.Wai.Middleware,

do (middleware, lookup) <- do
      key <- Wai.CryptoCookie.autoKeyFileBase16 "/run/my-cookie-encryption-key"
      Wai.CryptoCookie.middleware (Wai.CryptoCookie.defaultConfig key)

The obtained middleware shall be applied to your Network.Wai.Application.

The obtained lookup function can be used to obtain the CryptoCookie associated with each Request. It returns Nothing if this particular middleware was not used on the given Request.

Finally, interact with the CryptoCookie data using get or set.

Do I store session data on the client or on the server?

It's not so much about where to store the session data, but about how to store it and how to expire it. Here are some ideas. But please, do your own research.

  1. Data on server, identifier on both: In this approach, all the data is stored on the server. On the CryptoCookie, simply set a unique session identifier and later get it back and use it to find the associated session data on your server-side database. In order to expire the session, all the server have to do is remove this session identifier from its database. Choose this option unless you know what you are doing, it doesn't require you to plan ahead too much.
  2. Data on the client, identifier on both: In this approach, all the data and session identifier is stored on the CryptoCookie. On your server-side database, store the session identifier and a timestamp representing its creation time or last session activity time. Before accepting the session data from the CryptoCookie as valid, check that the session identifier exists in your database, and that the time since the timestamp is acceptable. This approach is simpler on your server-side database, but it can lead to more network traffic, and schema migrations for session data will be complex if you care about backwards compatibility with currently active sessions.
  3. Everything on the client: You can store everything in the CryptoCookie. However, you will be more suceptible to replay attacks because you won't have control over session expiration beyond comparing the current time against the session creation timestamp or last activity timestamp previously set in the session data. You can force all the existing sessions to “expire” by rotating your encryption Key. Also, this approach can lead to more network traffic, and schema migrations for session data will be complex if you care about backwards compatibility with currently active sessions.
Synopsis

Cookie data

data CryptoCookie a Source #

Read-write access to the Wai.CryptoCookie data.

See get and set.

get :: CryptoCookie a -> Maybe a Source #

The data that came through the Request cookie, if any.

set :: CryptoCookie a -> Maybe a -> STM () Source #

Cause the eventual Response corresponding to the current Request to set the cookie to the specified value if Just, or expire (delete) the cookie if Nothing.

Overrides previous uses of set.

Middleware

middleware Source #

Arguments

:: forall a m. MonadIO m 
=> Config a

Consider using defaultConfig.

-> m (Middleware, Request -> Maybe (CryptoCookie a)) 

Construct a new Middleware, and function that can be used to look-up the CryptoCookie on each incoming Request. Said function returns Nothing if the Middleware was not used on the Request.

defaultConfig Source #

Arguments

:: (FromJSON a, ToJSON a) 
=> Key "AEAD_AES_256_GCM_SIV"

Consider using autoKeyFileBase16 or readKeyFileBase16 for safely reading a Key from a FilePath. Alternatively, if you have the base-16 representation of the Key in JSON configuration, you coulud use FromJSON.

-> Config a 

Default Config:

  • Encoding is aeson.
  • Encryption scheme is the nonce-misuse resistant AEAD_AES_256_GCM_SIV as defined in in RFC 8452.

    As an AEAD encryption scheme, you can be confident that a successfully decrypted cookie could only have been encrypted by the same Key. This makes this encryption scheme suitable for storing user session authentication identifiers generated by the server.

  • Cookie name is SESSION.

    • HttpOnly: yes
    • Max-Age: 16 hours
    • Path: /
    • SameSite: Lax
    • Secure: yes
    • Domain: not set

data Config a Source #

Configuration for middleware.

Consider using defaultConfig and updating desired fields only.

Constructors

Encryption e => Config 

Fields

autoKeyFileBase16 :: forall {k} (e :: k) m. (Encryption e, MonadIO m) => FilePath -> m (Key e) Source #

If the FilePath exists, then read the base-16 representation of a Key from it. Ignores trailing newlines.

Otherwise, generate a random new Key and write its base-16 representation in the FilePath.

Finally, return the Key.

readKeyFileBase16 :: forall {k} (e :: k) m. (Encryption e, MonadIO m) => FilePath -> m (Key e) Source #

Read a base-16 encoded Key from a file. Ignores trailing newlines.