diff --git a/COPYING b/COPYING
new file mode 100644
--- /dev/null
+++ b/COPYING
@@ -0,0 +1,29 @@
+Copyright (c) 2012 Dag Odenhall
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+Neither the name of the HAppS.org; nor the names of its contributors
+may be used to endorse or promote products derived from this software
+without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,3 @@
+#!/usr/bin/env runhaskell
+import Distribution.Simple
+main = defaultMainWithHooks simpleUserHooks
diff --git a/happstack-clientsession.cabal b/happstack-clientsession.cabal
new file mode 100644
--- /dev/null
+++ b/happstack-clientsession.cabal
@@ -0,0 +1,32 @@
+Name          : happstack-clientsession
+Version       : 7.0.0
+License       : BSD3
+License-file  : COPYING
+Synopsis      : client-side session data
+Description   : uses the clientsession library to store session data in an HTTP cookie
+Author        : Dag Odenhall
+Maintainer    : Happstack team <happs@googlegroups.com>
+Homepage      : http://happstack.com
+Cabal-Version : >= 1.6
+Category      : Web, Happstack
+Build-Type    : Simple
+
+source-repository head
+    type:     darcs
+    subdir:   happstack-clientsession
+    location: http://patch-tag.com/r/mae/happstack
+
+
+Library
+  GHC-Options     : -Wall
+  Hs-Source-Dirs  : src
+  Exposed-Modules :
+    Happstack.Server.ClientSession
+  Build-Depends   :
+    base             == 4.*,
+    bytestring       == 0.9.*,
+    cereal           == 0.3.*,
+    clientsession    == 0.7.*,
+    happstack-server == 7.0.*,
+    mtl              == 2.0.*,
+    safecopy         == 0.6.*
diff --git a/src/Happstack/Server/ClientSession.hs b/src/Happstack/Server/ClientSession.hs
new file mode 100644
--- /dev/null
+++ b/src/Happstack/Server/ClientSession.hs
@@ -0,0 +1,147 @@
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE RecordWildCards #-}
+
+module Happstack.Server.ClientSession
+  ( ClientSession(..)
+  , SessionConf(..)
+  , mkSessionConf
+  , ClientSessionT
+  , runClientSessionT
+  , getSession
+  , putSession
+  , expireSession
+  , withSession
+  , sessionPart
+  ) where
+
+import Control.Applicative   (Applicative, Alternative, optional)
+import Control.Monad         (MonadPlus, when)
+import Control.Monad.Reader  (ReaderT, runReaderT, ask, asks)
+import Control.Monad.State   (StateT, State, evalStateT, runState, get, put)
+import Control.Monad.Trans   (MonadIO, liftIO)
+import Data.ByteString.Char8 (pack, unpack)
+import Data.SafeCopy         (SafeCopy, safeGet, safePut)
+import Data.Serialize        (runGet, runPut)
+import Happstack.Server      (HasRqData, FilterMonad, WebMonad, ServerMonad, Happstack, Response, CookieLife(Session), Cookie(secure), lookCookieValue, addCookie, mkCookie, expireCookie)
+import Web.ClientSession     (Key, decrypt, encryptIO)
+
+-- | Your session type must have an instance for this class.
+class SafeCopy st => ClientSession st where
+  -- | An empty session, i.e. what you get when there is no existing
+  -- session stored.
+  empty :: st
+
+data SessionState st = Encoded | Decoded st | Modified st | Expired
+
+-- | Configuration for the session cookie for passing to 'runClientSessionT'.
+data SessionConf = SessionConf
+    { sessionCookieName :: String      -- ^ Name of the cookie to hold your session data.
+    , sessionCookieLife :: CookieLife  -- ^ Lifetime of that cookie.
+    , sessionKey        :: Key         -- ^ Encryption key, usually from 'getKey' or 'getDefaultKey'.
+    , sessionSecure     :: Bool        -- ^ Only use a session over secure transports.
+    }
+
+-- | Create a 'SessionConf' using defaults for everything except
+-- 'sessionKey'.  You can use record update syntax to override individual
+-- fields.
+--
+-- > main = do key <- getDefaultKey
+-- >           let sessConf = (mkSessionConf key) { sessionCookieLife = oneWeek }
+-- >           simpleHTTP nullConf $ runClientSessionT handlers sessConf
+-- >   where
+-- >     oneWeek  = MaxAge $ 60 * 60 * 24 * 7
+-- >     handlers = sessionPart $ msum [...]
+mkSessionConf :: Key -> SessionConf
+mkSessionConf key = SessionConf
+    { sessionCookieName = "Happstack.ClientSession"
+    , sessionCookieLife = Session
+    , sessionKey        = key
+    , sessionSecure     = True
+    }
+
+-- | Transformer for monads capable of using a session.
+newtype ClientSessionT st m a =
+    ClientSessionT { unClientSessionT :: ReaderT SessionConf (StateT (SessionState st) m) a }
+  deriving ( Functor, Applicative, Alternative
+           , Monad, MonadIO, MonadPlus
+           , HasRqData, FilterMonad r, WebMonad r, ServerMonad
+           )
+
+instance Happstack m => Happstack (ClientSessionT st m)
+
+-- | Get the inner monad of a 'ClientSessionT'.
+runClientSessionT :: Monad m => ClientSessionT st m a -> SessionConf -> m a
+runClientSessionT cs sc =
+    evalStateT (runReaderT (unClientSessionT cs) sc) Encoded
+
+askCS :: Monad m => ClientSessionT st m SessionConf
+askCS = ClientSessionT ask
+
+asksCS :: Monad m => (SessionConf -> a) -> ClientSessionT st m a
+asksCS = ClientSessionT . asks
+
+getCS :: Monad m => ClientSessionT st m (SessionState st)
+getCS = ClientSessionT get
+
+putCS :: Monad m => SessionState st -> ClientSessionT st m ()
+putCS = ClientSessionT . put
+
+-- | Get the session value.  If the cookie has not been decoded yet, it
+-- will be decoded.  If no session data is stored or the session has been
+-- expired, 'empty' is returned.
+getSession :: (Functor m, MonadPlus m, HasRqData m, ClientSession st)
+           => ClientSessionT st m st
+getSession = do
+    ss <- getCS
+    case ss of
+      Decoded a  -> return a
+      Modified a -> return a
+      Expired    -> new
+      Encoded    -> do a <- getValue
+                       putCS $ Decoded a
+                       return a
+  where
+    new      = return empty
+    getValue = do name <- asksCS sessionCookieName
+                  value <- optional $ lookCookieValue name
+                  maybe new decode value
+    decode v = do key <- asksCS sessionKey
+                  maybe new (either (const new) return . runGet safeGet)
+                    . decrypt key $ pack v
+
+-- | Put a new value in the session.
+putSession :: (Monad m, ClientSession st) => st -> ClientSessionT st m ()
+putSession = putCS . Modified
+
+-- | Expire the session, i.e. the cookie holding it.
+expireSession :: Monad m => ClientSessionT st m ()
+expireSession = putCS Expired
+
+-- | Run a 'State' monad with the session.
+withSession :: (Functor m, MonadPlus m, HasRqData m, ClientSession st, Eq st)
+            => State st a -> ClientSessionT st m a
+withSession m = do s <- getSession
+                   let (a,st) = runState m s
+                   when (st /= s) $ putSession st
+                   return a
+
+-- | Wrapper around your handlers that use the session.  Takes care of
+-- expiring the cookie of an expired session, or encrypting a modified
+-- session into the cookie.
+sessionPart :: (Functor m, Monad m, MonadIO m, FilterMonad Response m, ClientSession st)
+            => ClientSessionT st m a -> ClientSessionT st m a
+sessionPart part = do
+    a  <- part
+    ss <- getCS
+    case ss of
+      Modified st -> encode st
+      Expired     -> expire
+      _           -> return ()
+    return a
+  where
+    encode st = do SessionConf{..} <- askCS
+                   bytes <- liftIO . encryptIO sessionKey . runPut . safePut $ st
+                   addCookie sessionCookieLife $ (mkCookie sessionCookieName $ unpack bytes) { secure = sessionSecure }
+    expire    = do name <- asksCS sessionCookieName
+                   expireCookie name
