diff --git a/Setup.hs b/Setup.hs
new file mode 100644
--- /dev/null
+++ b/Setup.hs
@@ -0,0 +1,2 @@
+import Distribution.Simple
+main = defaultMain
diff --git a/Web/Scotty/Session.hs b/Web/Scotty/Session.hs
new file mode 100644
--- /dev/null
+++ b/Web/Scotty/Session.hs
@@ -0,0 +1,126 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Web.Scotty.Session
+    ( createSessionManager
+    , modifySession
+    , readSession
+    , ScottySM
+    )
+where
+
+import Control.Concurrent
+import Control.Concurrent.STM
+import Control.Monad.IO.Class (liftIO)
+import Data.Monoid
+import Control.Arrow
+
+import qualified Data.Text as T
+import qualified Data.Text.Lazy as TL
+import qualified Data.Text.Encoding as TEnc
+import qualified Data.HashMap.Strict as HM
+
+import Crypto.Random (newGenIO, genBytes, SystemRandom)
+import Crypto.Types (ByteLength)
+import qualified Data.ByteString.Base64 as B64
+
+import Network.Wai
+
+import Data.Time.Clock
+import Data.Time.Format
+import System.Locale (defaultTimeLocale)
+
+import Web.Scotty
+
+data Session a
+   = Session
+   { sess_id         :: T.Text
+   , sess_validUntil :: UTCTime
+   , sess_content    :: Maybe a
+   } deriving (Show, Eq)
+type SessionJar a = TVar (HM.HashMap T.Text (Session a))
+
+newtype ScottySM a = ScottySM { _unSessionManager :: SessionJar a }
+
+-- | Create a new session manager
+createSessionManager :: ScottyM (ScottySM a)
+createSessionManager =
+    do storage <- liftIO $ atomically $ newTVar (HM.empty)
+       liftIO $ forkIO $ maintainSessions storage
+       return $ ScottySM storage
+
+-- | Modify the current users session
+modifySession :: ScottySM a -> (Maybe a -> Maybe a) -> ActionM ()
+modifySession sm@(ScottySM storage) fun =
+    do oldS <- readSession' sm id
+       let newS = oldS { sess_content = fun (sess_content oldS) }
+       liftIO $ insertSession newS storage
+
+-- | Read the current users session
+readSession :: ScottySM a -> ActionM (Maybe a)
+readSession sm = readSession' sm sess_content
+
+readSession' :: ScottySM a -> (Session a -> b) -> ActionM b
+readSession' (ScottySM storage) fun =
+    do mSession <- loadSession storage
+       case mSession of
+         Just s -> return $ fun s
+         Nothing ->
+             do newS <- liftIO $ createSession
+                liftIO $ insertSession newS storage
+                setCookie newS
+                return $ fun newS
+
+sessionTTL :: NominalDiffTime
+sessionTTL = 36000 -- in seconds
+
+sessionIdEntropy :: ByteLength
+sessionIdEntropy = 12
+
+createSession :: IO (Session a)
+createSession =
+    do gen <- g
+       sid <- case genBytes sessionIdEntropy gen of
+                 Left err -> fail $ show err
+                 Right (x, _) -> return $ TEnc.decodeUtf8 $ B64.encode x
+       now <- getCurrentTime
+       let validUntil = addUTCTime sessionTTL now
+       return $ Session sid validUntil Nothing
+    where g = newGenIO :: IO SystemRandom
+
+insertSession :: Session a -> SessionJar a -> IO ()
+insertSession sess sessions =
+    atomically $ modifyTVar sessions $ \m -> HM.insert (sess_id sess) sess m
+
+getSession :: T.Text -> SessionJar a -> IO (Maybe (Session a))
+getSession sessId sessions =
+    do s <- atomically $ readTVar sessions
+       return $ HM.lookup sessId s
+
+maintainSessions :: SessionJar a -> IO ()
+maintainSessions sessions =
+  do now <- getCurrentTime
+     let stillValid sess = (sess_validUntil sess) > now
+     atomically $ modifyTVar sessions $ \m -> HM.filter stillValid m
+     threadDelay 1000000
+     maintainSessions sessions
+
+setCookie :: Session a -> ActionM ()
+setCookie sess =
+    do let formattedExp = TL.pack $ formatTime defaultTimeLocale "%a, %d-%b-%Y %X %Z" (sess_validUntil sess)
+       header "Set-Cookie" $ "sid=" <> TL.fromStrict (sess_id sess) <> "; " <> formattedExp
+
+loadSession :: SessionJar a -> ActionM (Maybe (Session a))
+loadSession sessions =
+    do req <- request
+       liftIO $ getUserSession req sessions
+
+getUserSession :: Request -> SessionJar a -> IO (Maybe (Session a))
+getUserSession req sessions =
+    case lookup "cookie" (requestHeaders req) >>=
+         lookup "sid" . parseCookies . TEnc.decodeUtf8 of
+      Just sid -> lookupSession sid
+      Nothing -> return Nothing
+    where lookupSession sid = getSession sid sessions
+
+parseCookies :: T.Text -> [(T.Text, T.Text)]
+parseCookies = map parseCookie . T.splitOn ";" . T.concat . T.words
+    where parseCookie = first T.init . T.breakOnEnd "="
diff --git a/scotty-session.cabal b/scotty-session.cabal
new file mode 100644
--- /dev/null
+++ b/scotty-session.cabal
@@ -0,0 +1,29 @@
+Name:                scotty-session
+Version:             0.0.1
+Synopsis:            Adding session functionality to scotty
+Homepage:            https://github.com/agrafix/scotty-session
+Bug-reports:         https://github.com/agrafix/scotty-session/issues
+License:             BSD3
+Author:              Alexander Thiemann <mail@agrafix.net>
+Maintainer:          Alexander Thiemann <mail@agrafix.net>
+Copyright:           (c) 2013 Alexander Thiemann
+Category:            Web
+Stability:           experimental
+Build-type:          Simple
+Cabal-version:       >= 1.10
+Description:
+  Adding PHP-like session management to Scotty.
+  .
+  [Scotty] <http://hackage.haskell.org/package/scotty>
+
+Library
+  Exposed-modules:     Web.Scotty.Session
+  default-language:    Haskell2010
+  build-depends:       base >= 3 && < 5, scotty, text, unordered-containers, transformers, stm, bytestring, blaze-builder, old-locale, time, http-types,
+                       wai, base64-bytestring, crypto-api
+
+  GHC-options: -Wall -fno-warn-orphans -fno-warn-unused-do-bind
+
+source-repository head
+  type:     git
+  location: git://github.com/agrafix/scotty-session.git
