diff --git a/CHANGELOG.md b/CHANGELOG.md
new file mode 100644
--- /dev/null
+++ b/CHANGELOG.md
@@ -0,0 +1,4 @@
+0.3.2.0
+=======
+
+* Initial factor out from parent package.
diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,30 @@
+Copyright Anton Gushcha (c) 2016-2017
+
+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 NCrashed nor the names of other
+      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/README.md b/README.md
new file mode 100644
--- /dev/null
+++ b/README.md
@@ -0,0 +1,40 @@
+# servant-auth-token-persistent
+
+Storage backend on persistent for [servant-auth-token](https://github.com/NCrashed/servant-auth-token) server.
+
+An itegration of the backend is simple:
+``` haskell
+-- | Special monad for authorisation actions
+newtype AuthM a = AuthM { unAuthM :: PersistentBackendT IO a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadError ServantErr, HasStorage, HasAuthConfig)
+
+-- | Execution of authorisation actions that require 'AuthHandler' context
+runAuth :: AuthM a -> ServerM a
+runAuth m = do
+  cfg <- asks envAuthConfig
+  pool <- asks envPool
+  liftHandler $ ExceptT $ runPersistentBackendT cfg pool $ unAuthM m
+```
+
+Don't forget to add migration to your server startup (if you actually want automatic migrations):
+``` haskell
+-- | Create new server environment
+newServerEnv :: MonadIO m => ServerConfig -> m ServerEnv
+newServerEnv cfg = do
+  let authConfig = defaultAuthConfig
+  pool <- liftIO $ do
+    pool <- createPool cfg
+    -- run migrations
+    flip runSqlPool pool $ runMigration S.migrateAll
+    -- create default admin if missing one
+    _ <- runPersistentBackendT authConfig pool $ ensureAdmin 17 "admin" "123456" "admin@localhost"
+    return pool
+  let env = ServerEnv {
+        envConfig = cfg
+      , envAuthConfig = authConfig
+      , envPool = pool
+      }
+  return env
+```
+
+See a full example in [servant-auth-token-example-persistent](https://github.com/NCrashed/servant-auth-token/tree/master/example/persistent).
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/servant-auth-token-persistent.cabal b/servant-auth-token-persistent.cabal
new file mode 100644
--- /dev/null
+++ b/servant-auth-token-persistent.cabal
@@ -0,0 +1,65 @@
+name:                servant-auth-token-persistent
+version:             0.4.0.0
+synopsis:            Persistent backend for servant-auth-token server
+description:         Please see README.md
+homepage:            https://github.com/ncrashed/servant-auth-token#readme
+license:             BSD3
+license-file:        LICENSE
+author:              NCrashed
+maintainer:          ncrashed@gmail.com
+copyright:           2016 Anton Gushcha
+category:            Web
+build-type:          Simple
+extra-source-files:
+  README.md
+  CHANGELOG.md
+cabal-version:       >=1.10
+
+library
+  hs-source-dirs:      src
+  exposed-modules:
+    Servant.Server.Auth.Token.Persistent
+    Servant.Server.Auth.Token.Persistent.Schema
+  build-depends:
+      base                    >= 4.7    && < 5
+    , aeson-injector          >= 1.0    && < 1.1
+    , bytestring              >= 0.10   && < 0.11
+    , containers              >= 0.5    && < 0.6
+    , ghc-prim                >= 0.5    && < 0.6
+    , monad-control           >= 1.0    && < 1.1
+    , mtl                     >= 2.2    && < 2.3
+    , persistent              >= 2.2    && < 2.7
+    , persistent-postgresql   >= 2.2    && < 2.7
+    , persistent-template     >= 2.1    && < 2.7
+    , servant-server          >= 0.9    && < 0.10
+    , servant-auth-token      >= 0.4    && < 0.5
+    , servant-auth-token-api  >= 0.4    && < 0.5
+    , text                    >= 1.2    && < 1.3
+    , time                    >= 1.5    && < 1.7
+    , transformers            >= 0.4    && < 0.6
+    , transformers-base       >= 0.4    && < 0.5
+    , uuid                    >= 1.3    && < 1.4
+  default-language:    Haskell2010
+  default-extensions:
+    BangPatterns
+    DataKinds
+    DeriveGeneric
+    FlexibleContexts
+    FlexibleInstances
+    FunctionalDependencies
+    GADTs
+    GeneralizedNewtypeDeriving
+    KindSignatures
+    MultiParamTypeClasses
+    OverloadedStrings
+    QuasiQuotes
+    RecordWildCards
+    ScopedTypeVariables
+    TemplateHaskell
+    TupleSections
+    TypeFamilies
+    TypeOperators
+
+source-repository head
+  type:     git
+  location: https://github.com/ncrashed/servant-auth-token
diff --git a/src/Servant/Server/Auth/Token/Persistent.hs b/src/Servant/Server/Auth/Token/Persistent.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Server/Auth/Token/Persistent.hs
@@ -0,0 +1,109 @@
+module Servant.Server.Auth.Token.Persistent(
+    PersistentBackendT
+  , runPersistentBackendT
+  ) where
+
+import Control.Monad.Reader
+import Control.Monad.Trans.Control
+import Control.Monad.Except
+import Data.Aeson.WithField
+import Database.Persist
+import Database.Persist.Sql
+import Servant.Server
+import Servant.Server.Auth.Token.Monad
+import Servant.Server.Auth.Token.Model
+import Servant.Server.Auth.Token.Config
+
+import qualified Servant.Server.Auth.Token.Persistent.Schema as S
+
+-- | Monad transformer that implements storage backend
+newtype PersistentBackendT m a = PersistentBackendT { unPersistentBackendT :: ReaderT (AuthConfig, ConnectionPool) (ExceptT ServantErr (SqlPersistT m)) a }
+  deriving (Functor, Applicative, Monad, MonadIO, MonadError ServantErr)
+
+instance Monad m => MonadReader SqlBackend (PersistentBackendT m) where
+  ask = PersistentBackendT $ lift . lift $ ask
+  local f m = do
+    (cfg, pool) <- PersistentBackendT ask
+    em <- PersistentBackendT . lift . lift $ local f $ runExceptT $ runReaderT (unPersistentBackendT m) (cfg, pool)
+    case em of
+      Left er -> PersistentBackendT . lift $ throwError er
+      Right m' -> return m'
+
+instance Monad m => HasAuthConfig (PersistentBackendT m) where
+  getAuthConfig = fmap fst $ PersistentBackendT ask
+
+-- | Execute backend action with given connection pool.
+runPersistentBackendT :: MonadBaseControl IO m => AuthConfig -> ConnectionPool -> PersistentBackendT m a -> m (Either ServantErr a)
+runPersistentBackendT cfg pool ma = runSqlPool (runExceptT $ runReaderT (unPersistentBackendT ma) (cfg, pool)) pool
+
+-- | Convert entity struct to 'WithId' version
+toWithId :: (S.ConvertStorage a' a, S.ConvertStorage (Key a') i) => Entity a' -> WithId i a
+toWithId (Entity k v) = WithField (S.convertFrom k) (S.convertFrom v)
+
+-- | Helper to execute DB actions in backend monad
+liftDB :: Monad m => SqlPersistT m a -> PersistentBackendT m a
+liftDB = PersistentBackendT . lift . lift
+
+instance (MonadIO m) => HasStorage (PersistentBackendT m) where
+  getUserImpl = liftDB . fmap (fmap S.convertFrom) . get . S.convertTo
+  getUserImplByLogin = liftDB . fmap (fmap toWithId) . getBy . S.UniqueLogin
+  listUsersPaged page size = liftDB $ do
+    users <- selectList [] [Asc S.UserImplId, OffsetBy (fromIntegral $ page * size), LimitTo (fromIntegral size)]
+    total <- count ([] :: [Filter S.UserImpl])
+    return (fmap toWithId users, fromIntegral total)
+  getUserImplPermissions uid = liftDB . fmap (fmap toWithId) $ selectList [S.UserPermUser ==. S.convertTo uid] [Asc S.UserPermPermission]
+  deleteUserPermissions uid = liftDB $ deleteWhere [S.UserPermUser ==. S.convertTo uid]
+  insertUserPerm = liftDB . fmap S.convertFrom . insert . S.convertTo
+  insertUserImpl = liftDB . fmap S.convertFrom . insert . S.convertTo
+  replaceUserImpl i v = liftDB $ replace (S.convertTo i) (S.convertTo v)
+  deleteUserImpl = liftDB . delete . S.convertTo
+  hasPerm i p = liftDB $ do
+    c <- count [S.UserPermUser ==. S.convertTo i, S.UserPermPermission ==. p]
+    return $ c > 0
+  getFirstUserByPerm p = liftDB . fmap (fmap toWithId) $ do
+    mp <- selectFirst [S.UserPermPermission ==. p] []
+    case mp of
+      Nothing -> return Nothing
+      Just (Entity _ S.UserPerm{..}) -> fmap (Entity userPermUser) <$> get userPermUser
+  selectUserImplGroups i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupUsersUser ==. S.convertTo i] [Asc S.AuthUserGroupUsersGroup]
+  clearUserImplGroups i = liftDB $ deleteWhere [S.AuthUserGroupUsersUser ==. S.convertTo i]
+  insertAuthUserGroup = liftDB . fmap S.convertFrom . insert . S.convertTo
+  insertAuthUserGroupUsers = liftDB . fmap S.convertFrom . insert . S.convertTo
+  insertAuthUserGroupPerms = liftDB . fmap S.convertFrom . insert . S.convertTo
+  getAuthUserGroup = liftDB . fmap (fmap S.convertFrom) . get . S.convertTo
+  listAuthUserGroupPermissions i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupPermsGroup ==. S.convertTo i] [Asc S.AuthUserGroupPermsPermission]
+  listAuthUserGroupUsers i = liftDB . fmap (fmap toWithId) $ selectList [S.AuthUserGroupUsersGroup ==. S.convertTo i] [Asc S.AuthUserGroupUsersUser]
+  replaceAuthUserGroup i v = liftDB $ replace (S.convertTo i) (S.convertTo v)
+  clearAuthUserGroupUsers i = liftDB $ deleteWhere [S.AuthUserGroupUsersGroup ==. S.convertTo i]
+  clearAuthUserGroupPerms i = liftDB $ deleteWhere [S.AuthUserGroupPermsGroup ==. S.convertTo i]
+  deleteAuthUserGroup = liftDB . delete . S.convertTo
+  listGroupsPaged page size = liftDB $ do
+    groups <- selectList [] [Asc S.AuthUserGroupId, OffsetBy (fromIntegral $ page * size), LimitTo (fromIntegral size)]
+    total <- count ([] :: [Filter S.AuthUserGroup])
+    return (fmap toWithId groups, fromIntegral total)
+  setAuthUserGroupName i n = liftDB $ update (S.convertTo i) [S.AuthUserGroupName =. n]
+  setAuthUserGroupParent i mp = liftDB $ update (S.convertTo i) [S.AuthUserGroupParent =. fmap S.convertTo mp]
+  insertSingleUseCode = liftDB . fmap S.convertFrom . insert . S.convertTo
+  setSingleUseCodeUsed i mt = liftDB $ update (S.convertTo i) [S.UserSingleUseCodeUsed =. mt]
+  getUnusedCode c i t = liftDB . fmap (fmap toWithId) $ selectFirst ([
+          S.UserSingleUseCodeValue ==. c
+        , S.UserSingleUseCodeUser ==. S.convertTo i
+        , S.UserSingleUseCodeUsed ==. Nothing
+        ] ++ (
+            [S.UserSingleUseCodeExpire ==. Nothing]
+        ||. [S.UserSingleUseCodeExpire >=. Just t]
+    )) [Desc S.UserSingleUseCodeExpire]
+  invalidatePermamentCodes i t = liftDB $ updateWhere [
+      S.UserSingleUseCodeUser ==. S.convertTo i
+    , S.UserSingleUseCodeUsed ==. Nothing
+    , S.UserSingleUseCodeExpire ==. Nothing
+    ]
+    [S.UserSingleUseCodeUsed =. Just t]
+  selectLastRestoreCode i t = liftDB . fmap (fmap toWithId) $ selectFirst [S.UserRestoreUser ==. S.convertTo i, S.UserRestoreExpire >. t] [Desc S.UserRestoreExpire]
+  insertUserRestore = liftDB . fmap S.convertFrom . insert . S.convertTo
+  findRestoreCode i rc t = liftDB . fmap (fmap toWithId) $ selectFirst [S.UserRestoreUser ==. S.convertTo i, S.UserRestoreValue ==. rc, S.UserRestoreExpire >. t] [Desc S.UserRestoreExpire]
+  replaceRestoreCode i v = liftDB $ replace (S.convertTo i) (S.convertTo v)
+  findAuthToken i t = liftDB . fmap (fmap toWithId) $ selectFirst [S.AuthTokenUser ==. S.convertTo i, S.AuthTokenExpire >. t] []
+  findAuthTokenByValue t = liftDB . fmap (fmap toWithId) $ selectFirst [S.AuthTokenValue ==. t] []
+  insertAuthToken = liftDB . fmap S.convertFrom . insert . S.convertTo
+  replaceAuthToken i v = liftDB $ replace (S.convertTo i) (S.convertTo v)
diff --git a/src/Servant/Server/Auth/Token/Persistent/Schema.hs b/src/Servant/Server/Auth/Token/Persistent/Schema.hs
new file mode 100644
--- /dev/null
+++ b/src/Servant/Server/Auth/Token/Persistent/Schema.hs
@@ -0,0 +1,85 @@
+{-# LANGUAGE MagicHash #-}
+module Servant.Server.Auth.Token.Persistent.Schema where
+
+import Data.Text
+import Data.Time
+import Database.Persist.TH
+import GHC.Generics
+import GHC.Prim
+
+import Servant.API.Auth.Token
+
+import qualified Servant.Server.Auth.Token.Model as M
+
+share [mkPersist sqlSettings
+     , mkDeleteCascade sqlSettings
+     , mkMigrate "migrateAll"] [persistLowerCase|
+UserImpl
+  login       Login
+  password    Password     -- encrypted with salt
+  email       Email
+  UniqueLogin login
+  deriving Generic Show
+UserPerm
+  user        UserImplId
+  permission  Permission
+  deriving Generic Show
+AuthToken
+  value       SimpleToken
+  user        UserImplId
+  expire      UTCTime
+  deriving Generic Show
+UserRestore
+  value       RestoreCode
+  user        UserImplId
+  expire      UTCTime
+  deriving Generic Show
+UserSingleUseCode
+  value     SingleUseCode
+  user      UserImplId
+  expire    UTCTime Maybe -- Nothing is code that never expires
+  used      UTCTime Maybe
+  deriving Generic Show
+AuthUserGroup
+  name        Text
+  parent      AuthUserGroupId Maybe
+  deriving Generic Show
+AuthUserGroupUsers
+  group       AuthUserGroupId
+  user        UserImplId
+  deriving Generic Show
+AuthUserGroupPerms
+  group       AuthUserGroupId
+  permission  Permission
+  deriving Generic Show
+|]
+
+-- | Defines way to convert from persistent struct to model struct and vice versa.
+--
+-- Warning: default implementation is done via 'unsafeCoerce#', so make sure that
+-- structure of 'a' and 'b' is completely identical.
+class ConvertStorage a b | a -> b, b -> a where
+  -- | Convert to internal representation
+  convertTo   :: b -> a
+  convertTo = unsafeCoerce#
+  -- | Convert from internal representation
+  convertFrom :: a -> b
+  convertFrom = unsafeCoerce#
+
+instance ConvertStorage UserImpl M.UserImpl
+instance ConvertStorage UserPerm M.UserPerm
+instance ConvertStorage AuthToken M.AuthToken
+instance ConvertStorage UserRestore M.UserRestore
+instance ConvertStorage UserSingleUseCode M.UserSingleUseCode
+instance ConvertStorage AuthUserGroup M.AuthUserGroup
+instance ConvertStorage AuthUserGroupUsers M.AuthUserGroupUsers
+instance ConvertStorage AuthUserGroupPerms M.AuthUserGroupPerms
+
+instance ConvertStorage UserImplId M.UserImplId
+instance ConvertStorage UserPermId M.UserPermId
+instance ConvertStorage AuthTokenId M.AuthTokenId
+instance ConvertStorage UserRestoreId M.UserRestoreId
+instance ConvertStorage UserSingleUseCodeId M.UserSingleUseCodeId
+instance ConvertStorage AuthUserGroupId M.AuthUserGroupId
+instance ConvertStorage AuthUserGroupUsersId M.AuthUserGroupUsersId
+instance ConvertStorage AuthUserGroupPermsId M.AuthUserGroupPermsId
