diff --git a/CHANGELOG.md b/CHANGELOG.md
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,7 @@
+# 0.2.4.0
+
+- Add GitLab provider.
+
 # 0.2.3.1
 
 - Expose `discoverURI` in `Network.Wai.Middleware.Auth.OIDC`
diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -22,8 +22,8 @@
 Google and Github.
 
 Configuration is done using a yaml config file. Here is a sample file that will
-configure `wai-auth` to run a file server with google and github authentication
-on `http://localhost:3000`:
+configure `wai-auth` to run a file server with Google, GitHub, and GitLab
+authentication on `http://localhost:3000`:
 
 ```yaml
 app_root: "_env:APPROOT:http://localhost:3000"
@@ -46,6 +46,12 @@
     client_secret: "...oxW"
     email_white_list:
       - "^[a-zA-Z0-9._%+-]+@example.com$"
+  gitlab:
+    client_id: "...9cfc"
+    client_secret: "...f0d0"
+    app_name: "Dev App for wai-middleware-auth"
+    email_white_list:
+      - "^[a-zA-Z0-9._%+-]+@example.com$"
 ```
 
 Above configuration will also block access to users that don't have an email
@@ -57,8 +63,9 @@
 azuCFq0zEBkLSXhQrhliZzZD8Kblo...
 ```
 
-Make sure you have proper callback/redirect urls registered with google/github
-apps, eg: `http://localhost:3000/_auth_middleware/google/complete`.
+Make sure you have proper callback/redirect urls registered with
+google/github/gitlab apps, eg:
+`http://localhost:3000/_auth_middleware/google/complete`.
 
 After configuration file is ready, running application is very easy:
 
@@ -67,3 +74,34 @@
 Listening on port 3000
 ```
 
+### Reverse proxy
+
+To use a reverse proxy instead of a file server, replace `file_server` with
+`reverse_proxy`, eg:
+
+```yaml
+reverse_proxy:
+  host: myapp.example.com
+  port: 80
+```
+
+### Self-hosted GitLab
+
+The GitLab provider also supports using a self-hosted GitLab instance by
+setting the `gitlab_host` field.  In this case you may also want to override
+the `provider_info` to change the title, logo, and description.  For example:
+
+```yaml
+providers:
+  gitlab:
+    gitlab_host: gitlab.mycompany.com
+    client_id: "...9cfc"
+    client_secret: "...f0d0"
+    app_name: "Dev App for wai-middleware-auth"
+    email_white_list:
+      - "^[a-zA-Z0-9._%+-]+@mycompany.com$"
+    provider_info:
+      title: My Company's GitLab
+      logo_url: https://mycompany.com/logo.png
+      descr: Use your My Company GitLab account to access this page.
+```
diff --git a/app/Main.hs b/app/Main.hs
--- a/app/Main.hs
+++ b/app/Main.hs
@@ -1,3 +1,4 @@
+{-# LANGUAGE CPP #-}
 {-# LANGUAGE OverloadedStrings #-}
 {-# LANGUAGE RecordWildCards   #-}
 {-# LANGUAGE TemplateHaskell   #-}
@@ -8,6 +9,7 @@
 import           Network.Wai.Handler.Warp                  (run)
 import           Network.Wai.Middleware.Auth
 import           Network.Wai.Middleware.Auth.OAuth2
+import           Network.Wai.Middleware.Auth.OAuth2.Gitlab
 import           Network.Wai.Middleware.Auth.OAuth2.Github
 import           Network.Wai.Middleware.Auth.OAuth2.Google
 import           Network.Wai.Middleware.RequestLogger      (logStdout)
@@ -18,6 +20,14 @@
   = ConfigFile FilePath
   | KeyFile KeyOptions
 
+showHelpText :: ParseError
+showHelpText = ShowHelpText
+#if MIN_VERSION_optparse_applicative(0,16,0)
+                Nothing
+#endif
+
+
+
 basicSettingsParser :: String -> Parser BasicOptions
 basicSettingsParser version =
   (ConfigFile <$>
@@ -28,7 +38,7 @@
      (InfoMsg version)
      (long "version" <> short 'v' <> help "Current version.") <*
    abortOption
-     ShowHelpText
+     showHelpText
      (long "help" <> short 'h' <> help "Display this message.")) <|>
   (subparser
      (command
@@ -60,7 +70,7 @@
     (long "base64" <> short 'b' <>
      help "Produce a key in a base64 encoded form.") <*
   abortOption
-    ShowHelpText
+    showHelpText
     (long "help" <> short 'h' <> help "Display this message.")
 
 
@@ -76,7 +86,7 @@
   case opts of
     ConfigFile configFile -> do
       authConfig <- readAuthConfig configFile
-      mkMain authConfig [githubParser, googleParser, oAuth2Parser] $ \port app -> do
+      mkMain authConfig [gitlabParser, githubParser, googleParser, oAuth2Parser] $ \port app -> do
         putStrLn $ "Listening on port " ++ show port
         run port $ logStdout app
     KeyFile (KeyOptions {..}) -> do
diff --git a/src/Network/Wai/Middleware/Auth/OAuth2/Gitlab.hs b/src/Network/Wai/Middleware/Auth/OAuth2/Gitlab.hs
new file mode 100644
--- /dev/null
+++ b/src/Network/Wai/Middleware/Auth/OAuth2/Gitlab.hs
@@ -0,0 +1,140 @@
+{-# LANGUAGE OverloadedStrings #-}
+{-# LANGUAGE RecordWildCards   #-}
+module Network.Wai.Middleware.Auth.OAuth2.Gitlab
+    ( Gitlab(..)
+    , mkGitlabProvider
+    , gitlabParser
+    ) where
+import           Control.Exception.Safe               (catchAny)
+import           Data.Maybe                           (fromMaybe)
+import           Data.Aeson
+import qualified Data.ByteString                      as S
+import           Data.Proxy                           (Proxy (..))
+import qualified Data.Text                            as T
+import           Data.Text.Encoding                   (encodeUtf8)
+import           Network.HTTP.Simple                  (getResponseBody,
+                                                       httpJSON, parseRequest,
+                                                       setRequestHeaders)
+import           Network.HTTP.Types
+import qualified Network.OAuth.OAuth2                 as OA2
+import           Network.Wai.Auth.Internal            (decodeToken)
+import           Network.Wai.Auth.Tools               (getValidEmail)
+import           Network.Wai.Middleware.Auth.OAuth2
+import           Network.Wai.Middleware.Auth.Provider
+
+-- | Create a gitlab authentication provider
+--
+-- @since 0.2.4.0
+mkGitlabProvider
+  :: T.Text -- ^ Hostname of GitLab instance (e.g. @gitlab.com@)
+  -> T.Text -- ^ Name of the application as it is registered on gitlab
+  -> T.Text -- ^ @client_id@ from gitlab
+  -> T.Text -- ^ @client_secret@ from gitlab
+  -> [S.ByteString] -- ^ White list of posix regular expressions for emails
+  -- attached to gitlab account.
+  -> Maybe ProviderInfo -- ^ Replacement for default info
+  -> Gitlab
+mkGitlabProvider gitlabHost appName clientId clientSecret emailWhiteList mProviderInfo =
+  Gitlab
+    appName
+    ("https://" <> gitlabHost <> "/api/v4/user")
+    emailWhiteList
+    OAuth2
+    { oa2ClientId = clientId
+    , oa2ClientSecret = clientSecret
+    , oa2AuthorizeEndpoint = ("https://" <> gitlabHost <> "/oauth/authorize")
+    , oa2AccessTokenEndpoint = ("https://" <> gitlabHost <> "/oauth/token")
+    , oa2Scope = Just ["read_user"]
+    , oa2ProviderInfo = fromMaybe defProviderInfo mProviderInfo
+    }
+  where
+    defProviderInfo =
+      ProviderInfo
+      { providerTitle = "GitLab"
+      , providerLogoUrl =
+          "https://about.gitlab.com/images/press/logo/png/gitlab-icon-rgb.png"
+      , providerDescr = "Use your GitLab account to access this page."
+      }
+
+-- | Aeson parser for `Gitlab` provider.
+--
+-- @since 0.2.4.0
+gitlabParser :: ProviderParser
+gitlabParser = mkProviderParser (Proxy :: Proxy Gitlab)
+
+
+-- | Gitlab authentication provider
+data Gitlab = Gitlab
+  { gitlabAppName          :: T.Text
+  , gitlabAPIUserEndpoint  :: T.Text
+  , gitlabEmailWhitelist   :: [S.ByteString]
+  , gitlabOAuth2           :: OAuth2
+  }
+
+instance FromJSON Gitlab where
+  parseJSON =
+    withObject "Gitlab Provider Object" $ \obj -> do
+      gitlabHost <- obj .:? "gitlab_host"
+      appName <- obj .: "app_name"
+      clientId <- obj .: "client_id"
+      clientSecret <- obj .: "client_secret"
+      emailWhiteList <- obj .:? "email_white_list" .!= []
+      mProviderInfo <- obj .:? "provider_info"
+      return $
+        mkGitlabProvider
+          (fromMaybe "gitlab.com" gitlabHost)
+          appName
+          clientId
+          clientSecret
+          (map encodeUtf8 emailWhiteList)
+          mProviderInfo
+
+-- | Newtype wrapper for a gitlab user
+newtype GitlabEmail = GitlabEmail { gitlabEmail :: S.ByteString } deriving Show
+
+instance FromJSON GitlabEmail where
+  parseJSON = withObject "Gitlab Email" $ \ obj -> do
+    email <- obj .: "email"
+    return (GitlabEmail $ encodeUtf8 email)
+
+
+-- | Makes an API call to gitlab and retrieves user's verified email.
+-- Note: we only retrieve the PRIMARY email, because there is no way
+-- to tell whether secondary emails are verified or not.
+retrieveUser :: T.Text -> T.Text -> S.ByteString -> IO GitlabEmail
+retrieveUser appName userApiEndpoint accessToken = do
+  req <- parseRequest (T.unpack userApiEndpoint)
+  resp <- httpJSON $ setRequestHeaders headers req
+  return $ getResponseBody resp
+  where
+    headers =
+      [ ("Authorization", "Bearer " <> accessToken)
+      , ("User-Agent", encodeUtf8 appName)
+      ]
+
+
+instance AuthProvider Gitlab where
+  getProviderName _ = "gitlab"
+  getProviderInfo = getProviderInfo . gitlabOAuth2
+  handleLogin Gitlab {..} req suffix renderUrl onSuccess onFailure = do
+    let onOAuth2Success oauth2Tokens = do
+          catchAny
+            (do accessToken <-
+                  case decodeToken oauth2Tokens of
+                    Left err -> fail err
+                    Right tokens -> pure $ encodeUtf8 $ OA2.atoken $ OA2.accessToken tokens
+                email <-
+                  gitlabEmail <$>
+                  retrieveUser
+                    gitlabAppName
+                    gitlabAPIUserEndpoint
+                    accessToken
+                let mValidEmail = getValidEmail gitlabEmailWhitelist [email]
+                case mValidEmail of
+                  Just validEmail -> onSuccess validEmail
+                  Nothing ->
+                    onFailure status403 $
+                    "Your primary email address does not have permission to access this resource. " <>
+                    "Please contact the administrator.")
+            (\_err -> onFailure status501 "Issue communicating with gitlab")
+    handleLogin gitlabOAuth2 req suffix renderUrl onOAuth2Success onFailure
diff --git a/wai-middleware-auth.cabal b/wai-middleware-auth.cabal
--- a/wai-middleware-auth.cabal
+++ b/wai-middleware-auth.cabal
@@ -1,6 +1,6 @@
 cabal-version:       1.18
 name:                wai-middleware-auth
-version:             0.2.3.1
+version:             0.2.4.1
 synopsis:            Authentication middleware that secures WAI application
 description:         Please see the README and Haddocks at <https://www.stackage.org/package/wai-middleware-auth>
 license:             MIT
@@ -14,6 +14,7 @@
 library
   exposed-modules:     Network.Wai.Middleware.Auth
                        Network.Wai.Middleware.Auth.OAuth2
+                       Network.Wai.Middleware.Auth.OAuth2.Gitlab
                        Network.Wai.Middleware.Auth.OAuth2.Github
                        Network.Wai.Middleware.Auth.OAuth2.Google
                        Network.Wai.Middleware.Auth.OIDC
@@ -72,6 +73,7 @@
                      , cereal
                      , clientsession
                      , optparse-simple
+                     , optparse-applicative
                      , wai-extra
                      , wai-middleware-auth
                      , warp
