diff --git a/bower.json b/bower.json
index fb8d923..dfffcde 100644
--- a/bower.json
+++ b/bower.json
@@ -34,7 +34,9 @@
     "purescript-smolder": "^7.0.0",
     "purescript-aff": "^4.0.0",
     "purescript-random": "^3.0.0",
-    "purescript-refs": "^3.0.0"
+    "purescript-refs": "^3.0.0",
+    "purescript-simple-json": "^1.1.0",
+    "purescript-crypto": "^0.2.0"
   },
   "devDependencies": {
     "purescript-psci-support": "^3.0.0",
diff --git a/src/Hyper/Node/Session/Cookie.js b/src/Hyper/Node/Session/Cookie.js
new file mode 100644
index 0000000..59fefa0
--- /dev/null
+++ b/src/Hyper/Node/Session/Cookie.js
@@ -0,0 +1,7 @@
+"use strict";
+
+var crypto = require('crypto')
+
+exports.randString = function () {
+  return crypto.randomBytes(32).toString('hex');
+}
diff --git a/src/Hyper/Node/Session/Cookie.purs b/src/Hyper/Node/Session/Cookie.purs
new file mode 100644
index 0000000..b626756
--- /dev/null
+++ b/src/Hyper/Node/Session/Cookie.purs
@@ -0,0 +1,63 @@
+module Hyper.Node.Session.Cookie where
+import Prelude
+
+import Control.Monad.Eff (Eff)
+import Control.Monad.Eff.Class (class MonadEff, liftEff)
+import Data.Either (hush)
+import Data.Maybe (Maybe(..))
+import Data.Newtype (class Newtype, unwrap)
+import Data.String (Pattern(..), joinWith, split)
+import Hyper.Session (SessionID(..), class SessionStore)
+import Node.Buffer (BUFFER)
+import Node.Crypto (CRYPTO)
+import Node.Crypto.Cipher as Cipher
+import Node.Crypto.Decipher as Decipher
+import Node.Crypto.Hash as Hash
+import Node.Crypto.Hmac as Hmac
+import Simple.JSON (class ReadForeign, class WriteForeign, readJSON, writeJSON)
+
+foreign import randString :: forall e. Eff (buffer :: BUFFER, crypto :: CRYPTO | e) String
+
+type Key = { hmacKey :: String, cipherKey :: String }
+
+mkSecret :: forall e m. MonadEff (buffer :: BUFFER, crypto :: CRYPTO | e) m => m Key
+mkSecret = do
+  hmacKey <- liftEff randString
+  cipherKey <- liftEff randString
+  pure $ { hmacKey, cipherKey }
+
+newtype CookieStore session = CookieStore Key
+derive instance newtypeCookieStore :: Newtype (CookieStore session) _
+
+encrypt :: forall e m. MonadEff (crypto :: CRYPTO, buffer :: BUFFER | e) m => Key -> String -> m String
+encrypt { cipherKey, hmacKey } text = do
+  encrypted <- liftEff $ Cipher.hex Cipher.AES256 cipherKey text
+  hmac <- liftEff $ Hmac.hex Hash.SHA512 hmacKey encrypted
+  pure $ joinWith "," [hmac, encrypted]
+
+decrypt :: forall e m. MonadEff (crypto :: CRYPTO, buffer :: BUFFER | e) m => Key -> String -> m (Maybe String)
+decrypt { cipherKey, hmacKey } text = case split (Pattern ",") text of
+  [hmac, encrypted] ->
+    let
+      calcHmac = liftEff $ Hmac.hex Hash.SHA512 hmacKey encrypted
+      decryptWhen hmac' | hmac == hmac' =
+        Just <$> liftEff (Decipher.fromHex Cipher.AES256 cipherKey encrypted)
+      decryptWhen _ = pure Nothing
+    in
+      calcHmac >>= decryptWhen
+  _ -> pure Nothing
+
+instance sessionStoreCookieStore ::
+  ( ReadForeign session
+  , WriteForeign session
+  , Monad m
+  , MonadEff (buffer :: BUFFER, crypto :: CRYPTO | e) m
+  ) =>
+  SessionStore (CookieStore session) m session where
+  newSessionID _ = pure $ SessionID "new-id"
+  get store id = do
+    text <- decrypt (unwrap store) $ unwrap id
+    pure $ text >>= readJSON >>> hush
+  put store _ session = SessionID <$> encrypt (unwrap store) json
+    where json = writeJSON session
+  delete store _ = pure unit
diff --git a/src/Hyper/Node/Session/InMemory.purs b/src/Hyper/Node/Session/InMemory.purs
index 102546b..f4f4492 100644
--- a/src/Hyper/Node/Session/InMemory.purs
+++ b/src/Hyper/Node/Session/InMemory.purs
@@ -36,6 +36,7 @@ instance sessionStoreInMemorySessionStore :: ( Monad m
     liftEff do
       log ("Saving session: " <> unwrap id)
       modifyRef var $ Map.insert id session
+      pure id
 
   delete (InMemorySessionStore var) id = do
     liftEff do
diff --git a/src/Hyper/Session.purs b/src/Hyper/Session.purs
index 8fdaa1a..969b5cd 100644
--- a/src/Hyper/Session.purs
+++ b/src/Hyper/Session.purs
@@ -11,16 +11,16 @@ module Hyper.Session
        ) where
 
 import Prelude
-import Data.NonEmpty as NonEmpty
-import Data.StrMap as StrMap
-import Hyper.Cookies as Cookies
 import Control.IxMonad (ibind, ipure, (:>>=))
 import Data.Either (Either(..))
 import Data.Maybe (Maybe(Nothing, Just), maybe)
 import Data.Newtype (class Newtype, unwrap)
+import Data.NonEmpty as NonEmpty
 import Data.StrMap (StrMap)
+import Data.StrMap as StrMap
 import Hyper.Conn (Conn)
 import Hyper.Cookies (setCookie)
+import Hyper.Cookies as Cookies
 import Hyper.Middleware (Middleware, lift')
 import Hyper.Middleware.Class (getConn)
 import Hyper.Response (class Response, HeadersOpen)
@@ -34,7 +34,7 @@ derive instance newtypeSessionID :: Newtype SessionID _
 class SessionStore store m session | store -> m, store -> session where
   newSessionID :: store -> m SessionID
   get :: store -> SessionID -> m (Maybe session)
-  put :: store -> SessionID -> session -> m Unit
+  put :: store -> SessionID -> session -> m SessionID
   delete :: store -> SessionID -> m Unit
 
 type Sessions s = { key :: String, store :: s }
@@ -125,8 +125,8 @@ saveSession session = do
         | unwrap id' /= "" -> ipure id'
         | otherwise -> lift' (newSessionID conn.components.sessions.store)
       Nothing -> lift' (newSessionID conn.components.sessions.store)
-  lift' (put conn.components.sessions.store sessionId session)
-  setCookie conn.components.sessions.key (unwrap sessionId)
+  sessionId' <- lift' (put conn.components.sessions.store sessionId session)
+  setCookie conn.components.sessions.key (unwrap sessionId')
   where
     bind = ibind
 
@@ -148,6 +148,6 @@ deleteSession
      Unit
 deleteSession = do
   conn <- getConn
-  _ <- maybe (ipure unit) (lift' <<< delete conn.components.sessions.store) <$> currentSessionID
+  _ <- maybe (ipure unit) (lift' <<< delete conn.components.sessions.store) =<< currentSessionID
   -- TODO: Better delete?
   setCookie conn.components.sessions.key ""
diff --git a/test/Hyper/SessionSpec.purs b/test/Hyper/SessionSpec.purs
new file mode 100644
index 0000000..430ae4f
--- /dev/null
+++ b/test/Hyper/SessionSpec.purs
@@ -0,0 +1,126 @@
+module Hyper.SessionSpec where
+
+import Prelude
+import Control.IxMonad ((:*>))
+import Control.Monad.Aff (Aff)
+import Control.Monad.Aff.Class (liftAff)
+import Control.Monad.Eff.Class (liftEff)
+import Control.Monad.Eff.Console (CONSOLE)
+import Control.Monad.Eff.Random (RANDOM)
+import Control.Monad.Eff.Ref (REF)
+import Control.Monad.Writer.Trans (WriterT, execWriterT, runWriterT)
+import Data.Either (Either)
+import Data.Maybe (Maybe(..))
+import Data.Monoid (class Monoid)
+import Data.StrMap as StrMap
+import Data.Tuple (Tuple(..), fst)
+import Hyper.Conn (Conn)
+import Hyper.Cookies (Values, cookies)
+import Hyper.Middleware (Middleware, evalMiddleware, runMiddleware)
+import Hyper.Node.Session.Cookie (CookieStore(..), mkSecret)
+import Hyper.Node.Session.InMemory (newInMemorySessionStore)
+import Hyper.Response (HeadersOpen, class Response)
+import Hyper.Session (class SessionStore, delete, deleteSession, get, getSession, newSessionID, put, saveSession)
+import Hyper.Test.TestServer (TestRequest(..), TestResponse(..), defaultRequest)
+import Node.Buffer (BUFFER)
+import Node.Crypto (CRYPTO)
+import Test.Spec (Spec, it, describe)
+import Test.Spec.Assertions (shouldEqual, shouldNotEqual)
+
+type MyAff b state e = WriterT (TestResponse b state) (Aff e)
+
+saveSession' ::
+  forall b state e req res store c session.
+  Response res (MyAff b state e) b  =>
+  SessionStore store (MyAff b state e) session  =>
+  session ->
+  Middleware
+  (MyAff b state e)
+  (Conn
+    req
+    (res HeadersOpen)
+    { sessions :: { key :: String, store :: store }
+    , cookies :: Either String (StrMap.StrMap Values)
+    | c })
+  (Conn
+    req
+    (res HeadersOpen)
+    { sessions :: { key :: String, store :: store }
+    , cookies :: Either String (StrMap.StrMap Values)
+    | c })
+  Unit
+saveSession' = saveSession
+
+run :: forall w m a. Functor m => WriterT w m a -> m a
+run = runWriterT >>> map fst
+
+getCookie :: Array (Tuple String String) -> String
+getCookie [Tuple "Set-Cookie" c] = c
+getCookie  _ = ""
+
+testStore :: forall store session e b state e.
+  SessionStore store (MyAff b state (console :: CONSOLE | e)) session =>
+  Show session =>
+  Eq session =>
+  Monoid session =>
+  Aff (console :: CONSOLE | e) store -> session -> session -> Spec (console :: CONSOLE | e) Unit
+testStore store session session' = do
+  it "retrieves data that was stored" do
+    store' <- store
+    liftAff (session `shouldNotEqual` session')
+    id <- run $ newSessionID store'
+    id' <- run $ put store' id session
+    sessionOut <- run $ get store' id'
+    sessionOut `shouldEqual` Just session
+    id1 <- run $ newSessionID store'
+    id1' <- run $ put store' id1 session'
+    sessionOut' <- run $ get store' id1'
+    sessionOut' `shouldEqual` Just session'
+    sessionOutSecond <- run $ get store' id'
+    sessionOutSecond `shouldEqual` Just session
+    id2 <- run $ newSessionID store'
+    blankSession <- run $ get store' id2
+    blankSession `shouldEqual` Nothing
+    run $ delete store' id'
+    sessionOutDeleted <- run $ get store' id
+    sessionOutDeleted `shouldEqual` Nothing
+  it "works with getSession/saveSession/deleteSession" do
+    store' <- store
+    Tuple sessionOut _ <- { request: TestRequest defaultRequest
+                          , response: TestResponse Nothing [] []
+                          , components: { sessions: { key: "session", store: store' }
+                                        , cookies: unit }}
+                          # runMiddleware (cookies :*> getSession) >>> run
+    sessionOut `shouldEqual` Nothing
+    TestResponse _ headers _ <- { request: TestRequest defaultRequest
+                                , response: TestResponse Nothing [] []
+                                , components: { sessions: { key: "session", store: store' }
+                                              , cookies: unit }}
+                                # evalMiddleware (cookies :*> saveSession' session) >>> execWriterT
+    let newCookies = getCookie headers
+    Tuple sessionOut' _ <- { request: TestRequest defaultRequest { headers = StrMap.singleton "cookie" newCookies }
+                           , response: TestResponse Nothing [] []
+                           , components: { sessions: { key: "session", store: store' }
+                                         , cookies: unit }}
+                           # runMiddleware (cookies :*> getSession) >>> run
+    sessionOut' `shouldEqual` Just session
+    Tuple response (TestResponse _ headers' _)
+      <- { request: TestRequest defaultRequest { headers = StrMap.singleton "cookie" newCookies }
+         , response: TestResponse Nothing [] []
+         , components: { sessions: { key: "session", store: store' }
+                       , cookies: unit }}
+         # evalMiddleware (cookies :*> getSession :*> deleteSession) >>> runWriterT
+    let newCookies' = getCookie headers'
+    Tuple sessionOut' _ <- { request: TestRequest defaultRequest { headers = StrMap.singleton "cookie" newCookies' }
+                          , response: TestResponse Nothing [] []
+                          , components: { sessions: { key: "session", store: store' }
+                                        , cookies: unit }}
+                          # runMiddleware (cookies :*> getSession) >>> run
+    sessionOut' `shouldEqual` Nothing
+
+spec :: forall e. Spec (ref :: REF, console :: CONSOLE, random :: RANDOM, buffer :: BUFFER, crypto :: CRYPTO | e) Unit
+spec = do
+  describe "Hyper.Node.Session.InMemory" do
+    testStore (liftEff newInMemorySessionStore) "value1" "value2"
+  describe "Hyper.Node.Session.Cookie" do
+    testStore (CookieStore <$> mkSecret) "value1" "value2"