diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,6 +1,8 @@
-## [_Unreleased_](https://github.com/thoughtbot/yesod-auth-oauth2/compare/v0.7.0.1...main)
+## [_Unreleased_](https://github.com/thoughtbot/yesod-auth-oauth2/compare/v0.7.0.2...main)
 
-None
+## [v0.7.0.2](https://github.com/thoughtbot/yesod-auth-oauth2/compare/v0.7.0.1...v0.7.0.2)
+
+- Add Auth0 provider ([@hw202207](https://github.com/freckle/yesod-auth-oauth2/pull/162))
 
 ## [v0.7.0.1](https://github.com/thoughtbot/yesod-auth-oauth2/compare/v0.7.0.0...v0.7.0.1)
 
diff --git a/example/Main.hs b/example/Main.hs
--- a/example/Main.hs
+++ b/example/Main.hs
@@ -26,6 +26,7 @@
 import Data.ByteString.Lazy (fromStrict, toStrict)
 import qualified Data.Map as M
 import Data.Maybe (fromJust)
+import Data.String (IsString(fromString))
 import Data.Text (Text)
 import qualified Data.Text as T
 import Data.Text.Encoding (decodeUtf8)
@@ -35,6 +36,7 @@
 import System.Environment (getEnv)
 import Yesod
 import Yesod.Auth
+import Yesod.Auth.OAuth2.Auth0
 import Yesod.Auth.OAuth2.AzureAD
 import Yesod.Auth.OAuth2.BattleNet
 import Yesod.Auth.OAuth2.Bitbucket
@@ -47,13 +49,14 @@
 import Yesod.Auth.OAuth2.Salesforce
 import Yesod.Auth.OAuth2.Slack
 import Yesod.Auth.OAuth2.Spotify
+import Yesod.Auth.OAuth2.Twitch
 import Yesod.Auth.OAuth2.Upcase
 import Yesod.Auth.OAuth2.WordPressDotCom
 
 data App = App
-    { appHttpManager :: Manager
-    , appAuthPlugins :: [AuthPlugin App]
-    }
+  { appHttpManager :: Manager
+  , appAuthPlugins :: [AuthPlugin App]
+  }
 
 mkYesod "App" [parseRoutes|
     / RootR GET
@@ -62,28 +65,28 @@
 
 instance Yesod App where
     -- see https://github.com/thoughtbot/yesod-auth-oauth2/issues/87
-    approot = ApprootStatic "http://localhost:3000"
+  approot = ApprootStatic "http://localhost:3000"
 
 instance YesodAuth App where
-    type AuthId App = Text
-    loginDest _ = RootR
-    logoutDest _ = RootR
+  type AuthId App = Text
+  loginDest _ = RootR
+  logoutDest _ = RootR
 
-    -- Disable any attempt to read persisted authenticated state
-    maybeAuthId = return Nothing
+  -- Disable any attempt to read persisted authenticated state
+  maybeAuthId = return Nothing
 
-    -- Copy the Creds response into the session for viewing after
-    authenticate c = do
-        mapM_ (uncurry setSession)
-            $ [("credsIdent", credsIdent c), ("credsPlugin", credsPlugin c)]
-            ++ credsExtra c
+  -- Copy the Creds response into the session for viewing after
+  authenticate c = do
+    mapM_ (uncurry setSession)
+      $  [("credsIdent", credsIdent c), ("credsPlugin", credsPlugin c)]
+      ++ credsExtra c
 
-        return $ Authenticated "1"
+    return $ Authenticated "1"
 
-    authPlugins = appAuthPlugins
+  authPlugins = appAuthPlugins
 
 instance RenderMessage App FormMessage where
-    renderMessage _ _ = defaultFormMessage
+  renderMessage _ _ = defaultFormMessage
 
 -- brittany-disable-next-binding
 
@@ -126,37 +129,41 @@
 
 mkFoundation :: IO App
 mkFoundation = do
-    loadEnv
+  loadEnv
 
-    appHttpManager <- newManager tlsManagerSettings
-    appAuthPlugins <- sequence
-        -- When Providers are added, add them here and update .env.example.
-        -- Nothing else should need changing.
-        --
-        -- FIXME: oauth2BattleNet is quite annoying!
-        --
-        [ loadPlugin oauth2AzureAD "AZURE_AD"
-        , loadPlugin (oauth2BattleNet [whamlet|TODO|] "en") "BATTLE_NET"
-        , loadPlugin oauth2Bitbucket "BITBUCKET"
-        , loadPlugin oauth2ClassLink "CLASSLINK"
-        , loadPlugin (oauth2Eve Plain) "EVE_ONLINE"
-        , loadPlugin oauth2GitHub "GITHUB"
-        , loadPlugin oauth2GitLab "GITLAB"
-        , loadPlugin oauth2Google "GOOGLE"
-        , loadPlugin oauth2Nylas "NYLAS"
-        , loadPlugin oauth2Salesforce "SALES_FORCE"
-        , loadPlugin oauth2Slack "SLACK"
-        , loadPlugin (oauth2Spotify []) "SPOTIFY"
-        , loadPlugin oauth2WordPressDotCom "WORDPRESS_DOT_COM"
-        , loadPlugin oauth2Upcase "UPCASE"
-        ]
+  auth0Host      <- getEnv "AUTH0_HOST"
 
-    return App { .. }
-  where
-    loadPlugin f prefix = do
-        clientId <- getEnv $ prefix <> "_CLIENT_ID"
-        clientSecret <- getEnv $ prefix <> "_CLIENT_SECRET"
-        pure $ f (T.pack clientId) (T.pack clientSecret)
+  appHttpManager <- newManager tlsManagerSettings
+  appAuthPlugins <- sequence
+      -- When Providers are added, add them here and update .env.example.
+      -- Nothing else should need changing.
+      --
+      -- FIXME: oauth2BattleNet is quite annoying!
+      --
+    [ loadPlugin oauth2AzureAD                            "AZURE_AD"
+    , loadPlugin (oauth2Auth0Host $ fromString auth0Host) "AUTH0"
+    , loadPlugin (oauth2BattleNet [whamlet|TODO|] "en")   "BATTLE_NET"
+    , loadPlugin oauth2Bitbucket                          "BITBUCKET"
+    , loadPlugin oauth2ClassLink                          "CLASSLINK"
+    , loadPlugin (oauth2Eve Plain)                        "EVE_ONLINE"
+    , loadPlugin oauth2GitHub                             "GITHUB"
+    , loadPlugin oauth2GitLab                             "GITLAB"
+    , loadPlugin oauth2Google                             "GOOGLE"
+    , loadPlugin oauth2Nylas                              "NYLAS"
+    , loadPlugin oauth2Salesforce                         "SALES_FORCE"
+    , loadPlugin oauth2Slack                              "SLACK"
+    , loadPlugin (oauth2Spotify [])                       "SPOTIFY"
+    , loadPlugin oauth2Twitch                             "TWITCH"
+    , loadPlugin oauth2WordPressDotCom                    "WORDPRESS_DOT_COM"
+    , loadPlugin oauth2Upcase                             "UPCASE"
+    ]
+
+  return App { .. }
+ where
+  loadPlugin f prefix = do
+    clientId     <- getEnv $ prefix <> "_CLIENT_ID"
+    clientSecret <- getEnv $ prefix <> "_CLIENT_SECRET"
+    pure $ f (T.pack clientId) (T.pack clientSecret)
 
 main :: IO ()
 main = runEnv 3000 =<< toWaiApp =<< mkFoundation
diff --git a/src/Yesod/Auth/OAuth2/Auth0.hs b/src/Yesod/Auth/OAuth2/Auth0.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Auth/OAuth2/Auth0.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+-- OAuth2 plugin for <https://auth0.com>
+--
+-- * Authenticates against specific auth0 tenant
+-- * Uses Auth0 user id (a.k.a [sub](https://auth0.com/docs/api/authentication#get-user-info)) as credentials identifier
+--
+module Yesod.Auth.OAuth2.Auth0
+  ( oauth2Auth0HostScopes
+  , oauth2Auth0Host
+  , defaultAuth0Scopes
+  ) where
+
+import Data.Aeson as Aeson
+import qualified Data.Text as T
+import Prelude
+import Yesod.Auth.OAuth2.Prelude
+
+-- | https://auth0.com/docs/api/authentication#get-user-info
+newtype User = User T.Text
+
+instance FromJSON User where
+  parseJSON = withObject "User" $ \o -> User <$> o .: "sub"
+
+-- | https://auth0.com/docs/get-started/apis/scopes/openid-connect-scopes#standard-claims
+defaultAuth0Scopes :: [Text]
+defaultAuth0Scopes = ["openid"]
+
+pluginName :: Text
+pluginName = "auth0"
+
+oauth2Auth0Host :: YesodAuth m => URI -> Text -> Text -> AuthPlugin m
+oauth2Auth0Host host = oauth2Auth0HostScopes host defaultAuth0Scopes
+
+oauth2Auth0HostScopes
+  :: YesodAuth m => URI -> [Text] -> Text -> Text -> AuthPlugin m
+oauth2Auth0HostScopes host scopes clientId clientSecret =
+  authOAuth2 pluginName oauth2 $ \manager token -> do
+    (User uid, userResponse) <- authGetProfile pluginName
+                                               manager
+                                               token
+                                               (host `withPath` "/userinfo")
+    pure Creds { credsPlugin = pluginName
+               , credsIdent  = uid
+               , credsExtra  = setExtra token userResponse
+               }
+ where
+  oauth2 = OAuth2
+    { oauth2ClientId          = clientId
+    , oauth2ClientSecret      = Just clientSecret
+    , oauth2AuthorizeEndpoint = host
+                                `withPath`  "/authorize"
+                                `withQuery` [scopeParam " " scopes]
+    , oauth2TokenEndpoint     = host `withPath` "/oauth/token"
+    , oauth2RedirectUri       = Nothing
+    }
diff --git a/src/Yesod/Auth/OAuth2/Twitch.hs b/src/Yesod/Auth/OAuth2/Twitch.hs
new file mode 100644
--- /dev/null
+++ b/src/Yesod/Auth/OAuth2/Twitch.hs
@@ -0,0 +1,56 @@
+{-# LANGUAGE OverloadedStrings #-}
+-- |
+--
+-- OAuth2 plugin for http://twitch.tv
+--
+-- * Authenticates against twitch
+-- * Uses twitch user id as credentials identifier
+--
+module Yesod.Auth.OAuth2.Twitch
+  ( oauth2Twitch
+  , oauth2TwitchScoped
+  ) where
+
+import Yesod.Auth.OAuth2.Prelude
+
+import qualified Data.Text.Encoding as T
+
+newtype User = User Text
+
+instance FromJSON User where
+  parseJSON = withObject "User" $ \o -> User <$> o .: "user_id"
+
+pluginName :: Text
+pluginName = "twitch"
+
+defaultScopes :: [Text]
+defaultScopes = ["user:read:email"]
+
+oauth2Twitch :: YesodAuth m => Text -> Text -> AuthPlugin m
+oauth2Twitch = oauth2TwitchScoped defaultScopes
+
+oauth2TwitchScoped :: YesodAuth m => [Text] -> Text -> Text -> AuthPlugin m
+oauth2TwitchScoped scopes clientId clientSecret =
+  authOAuth2 pluginName oauth2 $ \manager token -> do
+    (User userId, userResponse) <- authGetProfile
+      pluginName
+      manager
+      token
+      "https://id.twitch.tv/oauth2/validate"
+
+    pure Creds { credsPlugin = pluginName
+               , credsIdent  = userId
+               , credsExtra  = setExtra token userResponse
+               }
+ where
+  oauth2 = OAuth2
+    { oauth2ClientId          = clientId
+    , oauth2ClientSecret      = Just clientSecret
+    , oauth2AuthorizeEndpoint = "https://id.twitch.tv/oauth2/authorize"
+                                  `withQuery` [scopeParam " " scopes]
+    , oauth2TokenEndpoint     = "https://id.twitch.tv/oauth2/token"
+                                  `withQuery` [ ("client_id", T.encodeUtf8 clientId)
+                                              , ("client_secret", T.encodeUtf8 clientSecret)
+                                              ]
+    , oauth2RedirectUri       = Nothing
+    }
diff --git a/yesod-auth-oauth2.cabal b/yesod-auth-oauth2.cabal
--- a/yesod-auth-oauth2.cabal
+++ b/yesod-auth-oauth2.cabal
@@ -1,23 +1,23 @@
-cabal-version:      1.12
-name:               yesod-auth-oauth2
-version:            0.7.0.1
-license:            MIT
-license-file:       LICENSE
-maintainer:         engineering@freckle.com
+cabal-version:   1.18
+name:            yesod-auth-oauth2
+version:         0.7.0.2
+license:         MIT
+license-file:    LICENSE
+maintainer:      engineering@freckle.com
 author:
     Tom Streller,
     Patrick Brisbin,
     Freckle Engineering
 
-homepage:           http://github.com/freckle/yesod-auth-oauth2
-bug-reports:        https://github.com/freckle/yesod-auth-oauth2/issues
-synopsis:           OAuth 2.0 authentication plugins
+homepage:        http://github.com/freckle/yesod-auth-oauth2
+bug-reports:     https://github.com/freckle/yesod-auth-oauth2/issues
+synopsis:        OAuth 2.0 authentication plugins
 description:
     Library to authenticate with OAuth 2.0 for Yesod web applications.
 
-category:           Web
-build-type:         Simple
-extra-source-files:
+category:        Web
+build-type:      Simple
+extra-doc-files:
     README.md
     CHANGELOG.md
 
@@ -35,6 +35,7 @@
         UnliftIO.Except
         URI.ByteString.Extension
         Yesod.Auth.OAuth2
+        Yesod.Auth.OAuth2.Auth0
         Yesod.Auth.OAuth2.AzureAD
         Yesod.Auth.OAuth2.BattleNet
         Yesod.Auth.OAuth2.Bitbucket
@@ -53,6 +54,7 @@
         Yesod.Auth.OAuth2.Salesforce
         Yesod.Auth.OAuth2.Slack
         Yesod.Auth.OAuth2.Spotify
+        Yesod.Auth.OAuth2.Twitch
         Yesod.Auth.OAuth2.Upcase
         Yesod.Auth.OAuth2.WordPressDotCom
 
