diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2015 Alexander Thiemann <mail@athiemann.net>
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be included
+in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
+IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
+CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
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/src/Web/Users/Persistent.hs b/src/Web/Users/Persistent.hs
new file mode 100644
--- /dev/null
+++ b/src/Web/Users/Persistent.hs
@@ -0,0 +1,279 @@
+{-# LANGUAGE CPP #-}
+{-# LANGUAGE DeriveDataTypeable #-}
+{-# LANGUAGE EmptyDataDecls #-}
+{-# LANGUAGE FlexibleContexts #-}
+{-# LANGUAGE FlexibleInstances #-}
+{-# LANGUAGE GADTs #-}
+{-# LANGUAGE GeneralizedNewtypeDeriving #-}
+{-# LANGUAGE MultiParamTypeClasses #-}
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE QuasiQuotes #-}
+{-# LANGUAGE RankNTypes #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE StandaloneDeriving #-}
+{-# LANGUAGE TemplateHaskell #-}
+{-# LANGUAGE TypeFamilies  #-}
+{-# LANGUAGE TypeSynonymInstances #-}
+module Web.Users.Persistent (LoginId, Persistent(..)) where
+
+import Web.Users.Types
+
+import Control.Applicative ((<$>), (<|>))
+import Control.Monad
+import Control.Monad.Reader
+#if MIN_VERSION_mtl(2,2,0)
+import Control.Monad.Except
+#else
+import Control.Monad.Error
+#endif
+import Data.Aeson
+import Data.Typeable
+import Data.Time.Clock
+import Database.Persist
+import Database.Persist.Sql
+import Database.Persist.TH
+import qualified Data.ByteString.Char8 as BSC
+import qualified Data.ByteString.Lazy as BSL
+import qualified Data.Text as T
+import qualified Data.UUID as UUID
+import qualified Data.UUID.V4 as UUID
+
+share [mkPersist sqlSettings, mkMigrate "migrateAll"] [persistLowerCase|
+Login
+    createdAt UTCTime
+    username T.Text
+    email T.Text
+    password T.Text
+    active Bool
+    more BSC.ByteString
+    UniqueUsername username
+    UniqueEmail email
+    deriving Show
+    deriving Eq
+    deriving Typeable
+LoginToken
+    token T.Text
+    tokenType T.Text
+    createdAt UTCTime
+    validUntil UTCTime
+    owner LoginId
+    UniqueToken token
+    UniqueTypedToken token tokenType
+    deriving Show
+    deriving Eq
+    deriving Typeable
+|]
+
+#if MIN_VERSION_base(4,7,0)
+deriving instance Typeable Key
+#else
+deriving instance Typeable1 Key
+#endif
+
+#if MIN_VERSION_mtl(2,2,0)
+type ErrorT = ExceptT
+runErrorT :: ErrorT e m a -> m (Either e a)
+runErrorT = runExceptT
+#else
+-- a hack... :-(
+instance Error UpdateUserError where
+    noMsg = error "Calling fail not supported"
+    strMsg = error "Calling fail not supported"
+#endif
+
+packLogin :: (Monad m, ToJSON a) => User a -> m (UTCTime -> Login)
+packLogin usr =
+    do p <-
+           case u_password usr of
+             PasswordHash p -> return p
+             _ -> fail "Invalid password! Not hashed!"
+       return $ \t ->
+           Login
+           { loginUsername = u_name usr
+           , loginEmail = u_email usr
+           , loginPassword = p
+           , loginActive = u_active usr
+           , loginMore = BSL.toStrict $ encode (u_more usr)
+           , loginCreatedAt = t
+           }
+
+unpackLogin :: (FromJSON a, Monad m) => Login -> m (User a)
+unpackLogin l =
+    do up <- unpackLogin' l
+       return $ up { u_password = PasswordHidden }
+
+unpackLogin' :: (FromJSON a, Monad m) => Login -> m (User a)
+unpackLogin' l =
+    do more <-
+           case eitherDecodeStrict' (loginMore l) of
+             Left err -> fail err
+             Right val -> return val
+       return $
+            User
+            { u_name = loginUsername l
+            , u_email = loginEmail l
+            , u_password = PasswordHash (loginPassword l)
+            , u_active = loginActive l
+            , u_more = more
+            }
+
+mkTuple :: (FromJSON a, Monad m) => Entity Login -> m (LoginId, User a)
+mkTuple entity =
+    do user <- unpackLogin (entityVal entity)
+       return (entityKey entity, user)
+
+newtype Persistent = Persistent { runPersistent :: forall a. SqlPersistT IO a -> IO a }
+
+instance UserStorageBackend Persistent where
+    type UserId Persistent = LoginId
+    initUserBackend conn =
+        runPersistent conn $ runMigration migrateAll
+    destroyUserBackend conn =
+        runPersistent conn $
+        do _ <- rawExecute "DROP TABLE IF EXISTS \"login\";" []
+           _ <- rawExecute "DROP TABLE IF EXISTS \"login_token\";" []
+           return ()
+    housekeepBackend conn =
+        do now <- getCurrentTime
+           runPersistent conn $ deleteWhere [LoginTokenValidUntil <=. now]
+    getUserIdByName conn userOrEmail =
+        runPersistent conn $
+        do mUserA <- getBy (UniqueUsername userOrEmail)
+           mUserB <- getBy (UniqueEmail userOrEmail)
+           return $ fmap entityKey (mUserA <|> mUserB)
+    getUserById conn loginId =
+        runPersistent conn $
+        do mUser <- get loginId
+           return $ join $ fmap unpackLogin mUser
+    listUsers conn mLimit =
+        runPersistent conn $
+        do xs <-
+               case mLimit of
+                 Nothing -> selectList [] []
+                 Just (start, lim) -> selectList [] [OffsetBy (fromIntegral start), LimitTo (fromIntegral lim)]
+           mapM mkTuple xs
+    countUsers conn =
+        liftM fromIntegral $
+        runPersistent conn $ count ([] :: [Filter Login])
+    createUser conn l =
+        case packLogin l of
+          Nothing -> return $ Left InvalidPassword
+          Just mkUser ->
+              do now <- getCurrentTime
+                 let usr = mkUser now
+                 runPersistent conn $
+                   do mFst <- selectFirst ([LoginUsername ==. loginUsername usr] ||. [LoginEmail ==. loginEmail usr]) []
+                      case mFst of
+                        Just _ -> return $ Left UsernameOrEmailAlreadyTaken
+                        Nothing -> Right <$> insert usr
+    updateUser conn userId updateFun =
+        do mUser <- getUserById conn userId
+           case mUser of
+             Nothing ->
+                 return $ Left UserDoesntExit
+             Just origUser ->
+                 runErrorT $
+                 do let newUser = updateFun origUser
+                    when (u_name newUser /= u_name origUser) $
+                         do counter <- liftIO $ runPersistent conn $ count [LoginUsername ==. u_name newUser]
+                            when (counter /= 0) $ throwError UsernameOrEmailAlreadyExists
+                    when (u_email newUser /= u_email origUser) $
+                         do counter <- liftIO $ runPersistent conn $ count [LoginEmail ==. u_email newUser]
+                            when (counter /= 0) $ throwError UsernameOrEmailAlreadyExists
+                    liftIO $ runPersistent conn $
+                       do update userId [ LoginUsername =. u_name newUser, LoginEmail =. u_email newUser, LoginActive =. u_active newUser
+                                        , LoginMore =. (BSL.toStrict $ encode $ u_more newUser) ]
+                          case u_password newUser of
+                            PasswordHash p -> update userId [ LoginPassword =. p ]
+                            _ -> return ()
+    deleteUser conn userId =
+        runPersistent conn $ delete userId
+    withAuthUser conn userOrEmail authFn action =
+        runPersistent conn $
+        do mFst <- selectFirst ([LoginUsername ==. userOrEmail] ||. [LoginEmail ==. userOrEmail]) []
+           case mFst of
+             Nothing -> return Nothing
+             Just login ->
+                 do user <- unpackLogin' (entityVal login)
+                    if authFn user
+                    then liftIO $ Just <$> action (entityKey login)
+                    else return Nothing
+    authUser conn userOrEmail pwd sessionTtl =
+        withAuthUser conn userOrEmail (\(user :: User Value) -> verifyPassword pwd $ u_password user) $ \userId ->
+            SessionId <$> createToken conn "session" userId sessionTtl
+    verifySession conn (SessionId sessionId) extendTime =
+        do mUser <- getTokenOwner conn "session" sessionId
+           case mUser of
+             Nothing -> return Nothing
+             Just userId ->
+                 do extendToken conn "session" sessionId extendTime
+                    return (Just userId)
+    destroySession conn (SessionId sessionId) = deleteToken conn "session" sessionId
+    requestPasswordReset conn userId timeToLive =
+        do token <- createToken conn "password_reset" userId timeToLive
+           return $ PasswordResetToken token
+    requestActivationToken conn userId timeToLive =
+        do token <- createToken conn "activation" userId timeToLive
+           return $ ActivationToken token
+    activateUser conn (ActivationToken token) =
+        do mUser <- getTokenOwner conn "activation" token
+           case mUser of
+             Nothing ->
+                 return $ Left TokenInvalid
+             Just userId ->
+                 do _ <-
+                        updateUser conn userId $ \(user :: User Value) -> user { u_active = True }
+                    deleteToken conn "activation" token
+                    return $ Right ()
+    verifyPasswordResetToken conn (PasswordResetToken token) =
+        do mUser <- getTokenOwner conn "password_reset" token
+           case mUser of
+             Nothing -> return Nothing
+             Just userId -> getUserById conn userId
+    applyNewPassword conn (PasswordResetToken token) password =
+        do mUser <- getTokenOwner conn "password_reset" token
+           case mUser of
+             Nothing ->
+                 return $ Left TokenInvalid
+             Just userId ->
+                 do _ <-
+                        updateUser conn userId $ \(user :: User Value) -> user { u_password = password }
+                    deleteToken conn "password_reset" token
+                    return $ Right ()
+
+createToken :: Persistent -> String -> LoginId -> NominalDiffTime -> IO T.Text
+createToken conn tokenType userId timeToLive =
+    runPersistent conn $
+    do tok <- liftM (T.pack . UUID.toString) $ liftIO $ UUID.nextRandom
+       now <- liftIO $ getCurrentTime
+       _ <- insert $ LoginToken tok (T.pack tokenType) now (timeToLive `addUTCTime` now) userId
+       return tok
+
+deleteToken :: Persistent -> String -> T.Text -> IO ()
+deleteToken conn tokenType token =
+    runPersistent conn $
+    case UUID.fromString (T.unpack token) of
+      Nothing -> return ()
+      Just _ ->
+          do deleteBy (UniqueTypedToken token (T.pack tokenType))
+             return ()
+
+extendToken :: Persistent -> String -> T.Text -> NominalDiffTime -> IO ()
+extendToken conn tokenType token timeToLive =
+    runPersistent conn $
+    case UUID.fromString (T.unpack token) of
+      Nothing -> return ()
+      Just _ ->
+          do now <- liftIO $ getCurrentTime
+             updateWhere [LoginTokenToken ==. token, LoginTokenTokenType ==. (T.pack tokenType)] [LoginTokenValidUntil =. (timeToLive `addUTCTime` now)]
+             return ()
+
+getTokenOwner :: Persistent -> String -> T.Text -> IO (Maybe LoginId)
+getTokenOwner conn tokenType token =
+    runPersistent conn $
+    case UUID.fromString (T.unpack token) of
+      Nothing -> return Nothing
+      Just _ ->
+          do now <- liftIO $ getCurrentTime
+             m <- selectFirst [LoginTokenTokenType ==. T.pack tokenType, LoginTokenToken ==. token, LoginTokenValidUntil >. now] []
+             return $ fmap (loginTokenOwner . entityVal) m
diff --git a/test/Spec.hs b/test/Spec.hs
new file mode 100644
--- /dev/null
+++ b/test/Spec.hs
@@ -0,0 +1,19 @@
+{-# LANGUAGE OverloadedStrings #-}
+module Main where
+
+import Web.Users.TestSpec
+import Web.Users.Persistent
+
+import System.IO.Temp
+import System.IO
+import Control.Monad.Logger
+import Database.Persist.Sqlite
+import Test.Hspec
+import qualified Data.Text as T
+
+main :: IO ()
+main =
+    withSystemTempFile "tempBaseXXX.db" $ \fp hdl ->
+    do hClose hdl
+       pool <- runNoLoggingT $ createSqlitePool (T.pack fp) 5
+       hspec $ makeUsersSpec (Persistent $ flip runSqlPool pool)
diff --git a/users-persistent.cabal b/users-persistent.cabal
new file mode 100644
--- /dev/null
+++ b/users-persistent.cabal
@@ -0,0 +1,59 @@
+name:                users-persistent
+version:             0.3.0.0
+synopsis:            A persistent backend for the users package
+description:         This library is a backend driver using <http://hackage.haskell.org/package/persistent persistent> for
+                     <http://hackage.haskell.org/package/users the "users" library>.
+                     .
+                     The package itself does not expose any bindings but provides an instance for 'UserStorageBackend'.
+                     .
+                     Usage:
+                     .
+                     > module Foo where
+                     > import Web.Users.Types
+                     > import Web.Users.Persistent
+                     > -- code goes here
+homepage:            https://github.com/agrafix/users
+license:             MIT
+license-file:        LICENSE
+author:              Alexander Thiemann <mail@athiemann.net>
+maintainer:          Alexander Thiemann <mail@athiemann.net>
+copyright:           (c) 2015 Alexander Thiemann
+category:            Data
+build-type:          Simple
+cabal-version:       >=1.10
+
+source-repository head
+  type: git
+  location: git://github.com/agrafix/users.git
+
+library
+  exposed-modules:     Web.Users.Persistent
+  build-depends:       base >=4.6 && <5,
+                       persistent >=2.0,
+                       persistent-template >=2.1,
+                       users >=0.3,
+                       mtl >=2.1,
+                       aeson >=0.7,
+                       time >=1.4,
+                       bytestring >=0.10,
+                       text >=1.2,
+                       uuid >=1.3
+  hs-source-dirs:      src
+  default-language:    Haskell2010
+  ghc-options:         -auto-all -Wall -fno-warn-orphans
+
+test-suite users-persistent-tests
+  type:                exitcode-stdio-1.0
+  hs-source-dirs:      test
+  main-is:             Spec.hs
+  build-depends:
+                       base >=4.6 && <5,
+                       hspec >=2.1,
+                       persistent-sqlite >=2.1,
+                       monad-logger >=0.3,
+                       temporary >=1.0,
+                       text,
+                       users-persistent,
+                       users-test
+  ghc-options:         -auto-all -Wall -fno-warn-orphans
+  default-language:    Haskell2010
