diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.1.2.0
+
+- Add Rails3 support.
+
 ## 0.1.1.0
 
 - Export DecryptedData type.
diff --git a/rails-session.cabal b/rails-session.cabal
--- a/rails-session.cabal
+++ b/rails-session.cabal
@@ -1,5 +1,5 @@
 name:                rails-session
-version:             0.1.1.0
+version:             0.1.2.0
 synopsis:            Decrypt Ruby on Rails sessions in Haskell
 description:         Please see README.md
 homepage:            http://github.com/iconnect/rails-session#readme
@@ -20,6 +20,7 @@
 library
   hs-source-dirs:      src
   exposed-modules:     Web.Rails.Session
+                     , Web.Rails3.Session
   build-depends:       base              >= 4.7 && < 5
                      , base-compat       >= 0.8.2
                      , base64-bytestring >= 1.0.0.1
@@ -30,6 +31,11 @@
                      , ruby-marshal      >= 0.1.1
                      , string-conv       >= 0.1
                      , vector            >= 0.10.12.3
+                     , bytestring
+                     , base16-bytestring
+                     , containers
+  if impl(ghc < 8.0)
+     build-depends: semigroups
   default-language:    Haskell2010
 
 test-suite specs
@@ -40,6 +46,7 @@
   build-depends:       base
                      , bytestring
                      , filepath
+                     , semigroups
                      , rails-session
                      , ruby-marshal
                      , tasty
diff --git a/src/Web/Rails3/Session.hs b/src/Web/Rails3/Session.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Rails3/Session.hs
@@ -0,0 +1,141 @@
+{-# LANGUAGE NoImplicitPrelude #-}
+{-# LANGUAGE OverloadedStrings #-}
+
+module Web.Rails3.Session
+  (
+    -- * Tutorial
+    -- $tutorial
+
+    -- * Decoding
+    decodeEither
+  , decode
+    -- * Utilities
+  , lookupUserIds
+    -- * Throw-away data-types
+    -- $datatypes
+  , Secret(..)
+  , Cookie(..)
+  )
+where
+
+import           Crypto.Hash                  as Hash
+import           Crypto.MAC.HMAC              as HMAC
+import           Data.ByteString
+import           Data.ByteString              as BS
+import qualified Data.ByteString.Base16       as B16
+import qualified Data.ByteString.Base64       as B64
+import qualified Data.Map.Strict              as Map
+import           Data.Ruby.Marshal            as Marshal hiding (decode, decodeEither)
+import qualified Data.Ruby.Marshal            as Marshal (decodeEither)
+import           Data.Ruby.Marshal.RubyObject
+import Network.HTTP.Types (urlDecode)
+import Data.List.NonEmpty as NE
+import Data.List as DL
+import Prelude (Either(..), (>>=), (.), (==), ($), Maybe(..), return, Num(..), Int, fromIntegral, Bool(..), fst, String, either, id, const)
+
+-- $tutorial
+--
+-- Here's how to decode a Rail3 session/auth cookie using 'wai' & 'cookie' package.
+--
+-- @
+-- import Network.Wai (requestHeaders)
+-- import Web.Cookie (parseCookies)
+-- ...
+--
+-- case (fmap (lookup "_yourapp_session") $ fmap parseCookies $ lookup "Cookie" $ requestHeaders waiRequest) of
+--
+-- -- no active Rails session
+-- Left _ -> ...
+--
+-- Right c -> case (decodeEither (Secret "yourSessionSecret") (Cookie c)) of
+--
+--   -- something went wrong in decoding the cookie. You should log "e" for debugging!
+--   Left e -> ...
+--
+--   Right obj -> case (lookupUserIds obj) of
+--
+--      -- we have a Rails session-cookie, but the user has not signed-in
+--     Nothing -> ...
+--
+--     -- signed-in user. This /may/ contain muliple userIds depending up on how you have configured Devise\/Warden in your Rails app.
+--     Just userIds -> ...
+-- @
+
+-- $datatypes
+--
+-- These data-types exist only as a way to semantically differentiate between
+-- various ByteString arguments when they are passed to functions. This is required
+-- only because Haskell doesn't have proper keywords-arguments.
+
+newtype Secret = Secret ByteString
+newtype Cookie = Cookie ByteString
+
+maybeToEither :: a -> Maybe b -> Either a b
+maybeToEither _ (Just b) = Right b
+maybeToEither a Nothing = Left a
+
+-- | Decode a cookie encoded by Rails3. Please read the documentation of
+-- 'decodeEither' for more details, and consider using 'decodeEither' instead of
+-- 'decode'
+decode :: Secret -> Cookie -> Maybe RubyObject
+decode s c = either (const Nothing) Just $ decodeEither s c
+
+-- | Decode a cookie encoded by Rails3. You can find the @Secret@ in a file
+-- called @config\/initializers\/secret_token.rb@ in your Rail3 app.
+--
+-- __Note:__ `decodeMaybe` has not been added on purpose. When cookie decoding
+-- fails, you would really want to know why. Please consider logging `Left`
+-- values returned by this function in your log, to save yourself some debugging
+-- time later.
+decodeEither :: Secret -> Cookie -> Either String RubyObject
+decodeEither (Secret cookieSecret) (Cookie x) =
+  extractChecksum
+  >>= compareChecksum
+  >>= Marshal.decodeEither
+  where
+    extractChecksum :: Either String (Digest SHA1)
+    extractChecksum = maybeToEither
+                      "[Rails3 Cookie] Illegal checksum in cookie. Wasn't able to extract a valid HMAC checksum out of it."
+                      (Hash.digestFromByteString $ fst $ (B16.decode hexChecksum))
+
+    compareChecksum :: Digest SHA1 -> Either String ByteString
+    compareChecksum checksum = if (computedChecksum == checksum) then (Right $ B64.decodeLenient b64) else (Left "[Rails3 Cookie] Checksum doesn't match")
+
+    computedChecksum :: Digest SHA1
+    computedChecksum = HMAC.hmacGetDigest (HMAC.hmac cookieSecret b64 :: HMAC SHA1)
+
+    (b64, hexChecksum) = let (a, b) = (breakSubstring delimiter $ urlDecode False x)
+                         in (a, BS.drop (BS.length delimiter) b)
+    delimiter = "--"
+
+
+-- NOTE: Please refer to
+-- http://blog.bigbinary.com/2013/03/19/cookies-on-rails.html to understand how
+-- a Rails3 cookie is encoded (NOT encyrpted). Encryption of session cookies
+-- only began in Rails4. Rails3 marshals a RubyObject and base64 encodes it to
+-- store it as a cookie. To ensure that it cannot be tamped with, it also adds
+-- an HMAC computed with the help of a secret key/value/token.
+
+safeHead :: [a] -> Maybe a
+safeHead []    = Nothing
+safeHead (x:_) = Just x
+
+lookupKey :: (Rubyable a) => (BS.ByteString, RubyStringEncoding) -> RubyObject -> Maybe a
+lookupKey key robj = (fromRuby robj :: Maybe (Map.Map (BS.ByteString, RubyStringEncoding) RubyObject))
+  >>= Map.lookup key
+  >>= fromRuby
+
+-- | Lookup the Warden\/Devise UserIds from a decoded cookie. __Please note,__ a
+-- cookie may contain multiple UserIds, because it /seems/ that it is possible
+-- to be logged-in as multiple users simultaneously, if you define [multiple
+-- user
+-- models](https://github.com/plataformatec/devise/wiki/How-to-Setup-Multiple-Devise-User-Models)
+-- (the underlying data-structure allows it, as well).
+lookupUserIds :: (Num a) => RubyObject -> Maybe (NonEmpty a)
+lookupUserIds robj =
+  lookupKey ("warden.user.user.key", UTF_8) robj
+  >>= (\x -> fromRuby x :: Maybe [RubyObject]) -- [[int, int int], "random string"]
+  >>= safeHead
+  >>= (\x -> fromRuby x :: Maybe [Int]) -- [int, int, int]
+  >>= (\xs -> return $ DL.map fromIntegral xs)
+  >>= NE.nonEmpty
diff --git a/test/Spec.hs b/test/Spec.hs
--- a/test/Spec.hs
+++ b/test/Spec.hs
@@ -11,14 +11,61 @@
 import           Test.Tasty (defaultMain, testGroup)
 import           Test.Tasty.Hspec (describe, it, shouldBe, shouldSatisfy, testSpec, Spec)
 import           Web.Rails.Session
+import qualified Web.Rails3.Session as R3
+import qualified Data.List.NonEmpty as NE
 
 -- SPECS
 
 main :: IO ()
 main = do
   rails4 <- testSpec "Web.Rails.Session: Rails4" $ specsFor Rails4
-  defaultMain (testGroup "All the Specs" [rails4])
+  rails3 <- testSpec "Web.Rails3.Session: Rails4" specsForRails3
+  defaultMain (testGroup "All the Specs" [rails4, rails3])
 
+specsForRails3 :: Spec
+specsForRails3 = do
+  let cookie = R3.Cookie $ unsafePerformIO $ BS.readFile "test/Rails3"
+      secret_ = R3.Secret "test secret token for testing cookies"
+      sessionIdVal_ = "3e0d672d2d594d02c8f015388ae380a8"
+      csrfTokenVal_ = "fKui2MZV3u5jhGBIgvvrOi7lo0mD/Jge3e0LOAS19Vg="
+      userIdVal_ = 107 :: Int
+      wardenContents_ = RArray (fromList [RArray (fromList [RFixnum userIdVal_]), RIVar (RString "$2a$10$hDu7wHneNw4A.6Wg61R4vO",UTF_8)])
+      cookieContents_ = RHash (fromList
+                          [ (RIVar (RString "session_id",UTF_8), RIVar (RString sessionIdVal_,UTF_8))
+                          , (RIVar (RString "warden.user.user.key",UTF_8), wardenContents_)
+                          , (RIVar (RString "_csrf_token",US_ASCII),RIVar (RString csrfTokenVal_,US_ASCII))
+                          ])
+
+
+  describe "all tests" $ do
+    it "should be a Right(..)" $ do
+      let result = R3.decodeEither secret_ cookie
+      result `shouldSatisfy` isRight
+
+    it "should be a fully-formed Ruby object" $ do
+      case R3.decodeEither secret_ cookie of
+        Left e -> error $ "decodeEither failed:" ++ (show e)
+        Right result -> do
+          result `shouldBe` cookieContents_
+
+    it "should look up the '_csrf_token'" $ do
+      case R3.decodeEither secret_ cookie of
+        Left e -> error $ "decodeEither failed:" ++ (show e)
+        Right result ->
+          csrfToken result `shouldBe` Just csrfTokenVal_
+
+    it "should look up the 'session_id'" $ do
+      case R3.decodeEither secret_ cookie of
+        Left e -> error $ "decodeEither failed:" ++ (show e)
+        Right result ->
+          sessionId result `shouldBe` Just sessionIdVal_
+
+    it "should look up the warden user_id(s)" $ do
+      case R3.decodeEither secret_ cookie of
+        Left e -> error $ "decodeEither failed:" ++ (show e)
+        Right result ->
+          R3.lookupUserIds result `shouldBe` Just (userIdVal_ NE.:| [] :: NE.NonEmpty Int)
+
 specsFor :: Rails -> Spec
 specsFor rails = do
   let cookie = unsafeReadCookie rails
@@ -33,7 +80,6 @@
         Left _ -> error "decode failed"
         Right result -> do
           result `shouldBe` rubySession
-
   describe "decrypt" $ do
     it "should be a Right(..)" $ do
       let result = decrypt Nothing secret cookie
@@ -90,7 +136,7 @@
 
 -- UTIL
 
-data Rails = Rails4 deriving (Show)
+data Rails = Rails4 | Rails3 deriving (Show)
 
 unsafeReadCookie :: Rails -> Cookie
 unsafeReadCookie rails = unsafePerformIO $
