diff --git a/LICENSE b/LICENSE
new file mode 100644
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,25 @@
+The following license covers this documentation, and the source code, except
+where otherwise indicated.
+
+Copyright 2008, Michael Snoyman. 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.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS "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 HOLDERS 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/Setup.lhs b/Setup.lhs
new file mode 100644
--- /dev/null
+++ b/Setup.lhs
@@ -0,0 +1,7 @@
+#!/usr/bin/env runhaskell
+
+> module Main where
+> import Distribution.Simple
+
+> main :: IO ()
+> main = defaultMain
diff --git a/Yesod/Auth/OAuth2.hs b/Yesod/Auth/OAuth2.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Auth/OAuth2.hs
@@ -0,0 +1,69 @@
+{-# LANGUAGE OverloadedStrings, QuasiQuotes #-}
+-- |
+--
+-- Generic OAuth2 plugin for Yesod
+--
+-- * See Yesod.Auth.OAuth2.Learn for example usage.
+--
+module Yesod.Auth.OAuth2
+    ( authOAuth2
+    , oauth2Url
+    , module Network.OAuth.OAuth2
+    ) where
+
+import Control.Monad.IO.Class
+import Data.ByteString (ByteString)
+import Data.Text (Text)
+import Data.Text.Encoding (decodeUtf8With, encodeUtf8)
+import Data.Text.Encoding.Error (lenientDecode)
+import Network.OAuth.OAuth2
+import Yesod.Auth
+import Yesod.Core
+import Yesod.Form
+
+oauth2Url :: Text -> AuthRoute
+oauth2Url name = PluginR name ["forward"]
+
+authOAuth2 :: YesodAuth m
+           => Text   -- ^ Service name
+           -> OAuth2 -- ^ Service details
+
+           -- | This function defines how to take an @'AccessToken'@ and
+           --   retrieve additional information about the user, to be
+           --   set in the session as @'Creds'@. Usually this means a
+           --   second authorized request to @api/me.json@.
+           -> (AccessToken -> IO (Creds m))
+           -> AuthPlugin m
+authOAuth2 name oauth getCreds = AuthPlugin name dispatch login
+
+    where
+        url = PluginR name ["callback"]
+
+        withCallback = do
+            tm <- getRouteToParent
+            render <- lift $ getUrlRender
+            return $ oauth { oauthCallback = Just $ encodeUtf8 $ render $ tm url }
+
+        dispatch "GET" ["forward"] = do
+            authUrl <- fmap (bsToText . authorizationUrl) withCallback
+            lift $ redirect authUrl
+
+        dispatch "GET" ["callback"] = do
+            code <- lift $ runInputGet $ ireq textField "code"
+            oauth' <- withCallback
+            result <- liftIO $ fetchAccessToken oauth' (encodeUtf8 code)
+            case result of
+                Left _ -> permissionDenied "Unable to retreive OAuth2 token"
+                Right token -> do
+                    creds <- liftIO $ getCreds token
+                    lift $ setCreds True creds
+
+        dispatch _ _ = notFound
+
+        login tm = do
+            render <- getUrlRender
+            let oaUrl = render $ tm $ oauth2Url name
+            [whamlet| <a href=#{oaUrl}>Login via #{name} |]
+
+bsToText :: ByteString -> Text
+bsToText = decodeUtf8With lenientDecode
diff --git a/Yesod/Auth/OAuth2/Google.hs b/Yesod/Auth/OAuth2/Google.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Auth/OAuth2/Google.hs
@@ -0,0 +1,30 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+--
+-- OAuth2 plugin for http://google.com
+--
+-- * Note: this module is unfinished, do not use.
+--
+module Yesod.Auth.OAuth2.Google
+    ( oauth2Google
+    , module Yesod.Auth.OAuth2
+    ) where
+
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+import Yesod.Auth
+import Yesod.Auth.OAuth2
+
+oauth2Google :: YesodAuth m
+             => Text -- ^ Client ID
+             -> Text -- ^ Client Secret
+             -> AuthPlugin m
+oauth2Google clientId clientSecret = authOAuth2 "google"
+    (OAuth2
+        { oauthClientId            = encodeUtf8 clientId
+        , oauthClientSecret        = encodeUtf8 clientSecret
+        , oauthOAuthorizeEndpoint  = "https://accounts.google.com/o/oauth2/auth"
+        , oauthAccessTokenEndpoint = "https://accounts.google.com/o/oauth2/token"
+        , oauthCallback            = Nothing
+        })
+    undefined -- TODO
diff --git a/Yesod/Auth/OAuth2/Learn.hs b/Yesod/Auth/OAuth2/Learn.hs
new file mode 100644
--- /dev/null
+++ b/Yesod/Auth/OAuth2/Learn.hs
@@ -0,0 +1,76 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+--
+-- OAuth2 plugin for http://learn.thoughtbot.com
+--
+-- * Authenticates against learn
+-- * Uses learn user id as credentials identifier
+-- * Returns first_name, last_name, and email as extras
+--
+module Yesod.Auth.OAuth2.Learn
+    ( oauth2Learn
+    , module Yesod.Auth.OAuth2
+    ) where
+
+import Control.Applicative ((<$>), (<*>))
+import Control.Monad (mzero)
+import Data.Aeson
+import Data.Text (Text)
+import Data.Text.Encoding (encodeUtf8)
+import Yesod.Auth
+import Yesod.Auth.OAuth2
+import qualified Data.Text as T
+
+data LearnUser = LearnUser
+    { learnUserId        :: Int
+    , learnUserFirstName :: Text
+    , learnUserLastName  :: Text
+    , learnUserEmail     :: Text
+    }
+
+instance FromJSON LearnUser where
+    parseJSON (Object o) =
+        LearnUser <$> o .: "id"
+                  <*> o .: "first_name"
+                  <*> o .: "last_name"
+                  <*> o .: "email"
+
+    parseJSON _ = mzero
+
+data LearnResponse = LearnResponse LearnUser
+
+instance FromJSON LearnResponse where
+    parseJSON (Object o) =
+        LearnResponse <$> o .: "user"
+
+    parseJSON _ = mzero
+
+oauth2Learn :: YesodAuth m
+            => Text -- ^ Client ID
+            -> Text -- ^ Client Secret
+            -> AuthPlugin m
+oauth2Learn clientId clientSecret = authOAuth2 "learn"
+    (OAuth2
+        { oauthClientId            = encodeUtf8 clientId
+        , oauthClientSecret        = encodeUtf8 clientSecret
+        , oauthOAuthorizeEndpoint  = "http://learn.thoughtbot.com/oauth/authorize"
+        , oauthAccessTokenEndpoint = "http://learn.thoughtbot.com/oauth/token"
+        , oauthCallback            = Nothing
+        })
+    fetchLearnProfile
+
+fetchLearnProfile :: AccessToken -> IO (Creds m)
+fetchLearnProfile token = do
+    result <- authGetJSON token "http://learn.thoughtbot.com/api/v1/me.json"
+
+    case result of
+        Right (LearnResponse user) -> return $ toCreds user
+        _ -> error "Invalid response for learn profile data"
+
+toCreds :: LearnUser -> Creds m
+toCreds user = Creds "learn"
+    (T.pack $ show $ learnUserId user)
+    [ ("first_name", learnUserFirstName user)
+    , ("last_name" , learnUserLastName user)
+    , ("email"     , learnUserEmail user)
+    ]
diff --git a/yesod-auth-oauth2.cabal b/yesod-auth-oauth2.cabal
new file mode 100644
--- /dev/null
+++ b/yesod-auth-oauth2.cabal
@@ -0,0 +1,43 @@
+name:            yesod-auth-oauth2
+version:         0.0.1
+license:         BSD3
+license-file:    LICENSE
+author:          Tom Streller
+maintainer:      Tom Streller
+synopsis:        Library to authenticate with OAuth 2.0 for Yesod web applications.
+description:     OAuth 2.0 authentication
+category:        Web
+stability:       Experimental
+cabal-version:   >= 1.6
+build-type:      Simple
+homepage:        http://github.com/scan/yesod-auth-oauth2
+
+flag ghc7
+
+library
+    if flag(ghc7)
+        build-depends:   base                >= 4.3      && < 5
+        cpp-options:     -DGHC7
+    else
+        build-depends:   base                >= 4         && < 4.3
+
+    build-depends:   bytestring              >= 0.9.1.4
+                   , http-conduit            >= 2.0       && < 3.0
+                   , http-types              >= 0.8       && < 0.9
+                   , aeson                   >= 0.6       && < 0.8
+                   , yesod-core              >= 1.2       && < 1.3
+                   , yesod-auth              >= 1.2       && < 1.3
+                   , text                    >= 0.7       && < 0.12
+                   , yesod-form              >= 1.3       && < 1.4
+                   , transformers            >= 0.2.2     && < 0.4
+                   , hoauth2                 >= 0.3.6     && < 0.4
+
+    exposed-modules: Yesod.Auth.OAuth2
+                     Yesod.Auth.OAuth2.Google
+                     Yesod.Auth.OAuth2.Learn
+
+    ghc-options:     -Wall
+
+source-repository head
+  type:     git
+  location: git://github.com/scan/authenticate-oauth2.git
