diff --git a/README.md b/README.md
index 9c7d78d2..b52b2d29 100644
--- a/README.md
+++ b/README.md
@@ -31,6 +31,7 @@ Valid providers are :
 * [Google](#google-auth-provider) *default*
 * [Azure](#azure-auth-provider)
 * [GitHub](#github-auth-provider)
+* [GitLab](#gitlab-auth-provider)
 * [LinkedIn](#linkedin-auth-provider)
 * [MyUSA](#myusa-auth-provider)
 
@@ -103,6 +104,17 @@ If you are using github enterprise, make sure you set the following to the appro
     -validate-url="<enterprise github api url>/user/emails"
 
 
+### GitLab Auth Provider
+
+Whether you are using GitLab.com or self-hosting GitLab, follow [these steps to add an application](http://doc.gitlab.com/ce/integration/oauth_provider.html)
+
+If you are using self-hosted GitLab, make sure you set the following to the appropriate URL:
+
+    -login-url="<your gitlab url>/oauth/authorize"
+    -redeem-url="<your gitlab url>/oauth/token"
+    -validate-url="<your gitlab url>/api/v3/user"
+
+
 ### LinkedIn Auth Provider
 
 For LinkedIn, the registration steps are:
diff --git a/providers/gitlab.go b/providers/gitlab.go
new file mode 100644
index 00000000..708283ad
--- /dev/null
+++ b/providers/gitlab.go
@@ -0,0 +1,58 @@
+package providers
+
+import (
+	"log"
+	"net/http"
+	"net/url"
+
+	"github.com/bitly/oauth2_proxy/api"
+)
+
+type GitLabProvider struct {
+	*ProviderData
+}
+
+func NewGitLabProvider(p *ProviderData) *GitLabProvider {
+	p.ProviderName = "GitLab"
+	if p.LoginURL == nil || p.LoginURL.String() == "" {
+		p.LoginURL = &url.URL{
+			Scheme: "https",
+			Host:   "gitlab.com",
+			Path:   "/oauth/authorize",
+		}
+	}
+	if p.RedeemURL == nil || p.RedeemURL.String() == "" {
+		p.RedeemURL = &url.URL{
+			Scheme: "https",
+			Host:   "gitlab.com",
+			Path:   "/oauth/token",
+		}
+	}
+	if p.ValidateURL == nil || p.ValidateURL.String() == "" {
+		p.ValidateURL = &url.URL{
+			Scheme: "https",
+			Host:   "gitlab.com",
+			Path:   "/api/v3/user",
+		}
+	}
+	if p.Scope == "" {
+		p.Scope = "api"
+	}
+	return &GitLabProvider{ProviderData: p}
+}
+
+func (p *GitLabProvider) GetEmailAddress(s *SessionState) (string, error) {
+
+	req, err := http.NewRequest("GET",
+		p.ValidateURL.String()+"?access_token="+s.AccessToken, nil)
+	if err != nil {
+		log.Printf("failed building request %s", err)
+		return "", err
+	}
+	json, err := api.Request(req)
+	if err != nil {
+		log.Printf("failed making request %s", err)
+		return "", err
+	}
+	return json.Get("email").String()
+}
diff --git a/providers/gitlab_test.go b/providers/gitlab_test.go
new file mode 100644
index 00000000..3df001c2
--- /dev/null
+++ b/providers/gitlab_test.go
@@ -0,0 +1,128 @@
+package providers
+
+import (
+	"net/http"
+	"net/http/httptest"
+	"net/url"
+	"testing"
+
+	"github.com/bmizerany/assert"
+)
+
+func testGitLabProvider(hostname string) *GitLabProvider {
+	p := NewGitLabProvider(
+		&ProviderData{
+			ProviderName: "",
+			LoginURL:     &url.URL{},
+			RedeemURL:    &url.URL{},
+			ProfileURL:   &url.URL{},
+			ValidateURL:  &url.URL{},
+			Scope:        ""})
+	if hostname != "" {
+		updateURL(p.Data().LoginURL, hostname)
+		updateURL(p.Data().RedeemURL, hostname)
+		updateURL(p.Data().ProfileURL, hostname)
+		updateURL(p.Data().ValidateURL, hostname)
+	}
+	return p
+}
+
+func testGitLabBackend(payload string) *httptest.Server {
+	path := "/api/v3/user"
+	query := "access_token=imaginary_access_token"
+
+	return httptest.NewServer(http.HandlerFunc(
+		func(w http.ResponseWriter, r *http.Request) {
+			url := r.URL
+			if url.Path != path || url.RawQuery != query {
+				w.WriteHeader(404)
+			} else {
+				w.WriteHeader(200)
+				w.Write([]byte(payload))
+			}
+		}))
+}
+
+func TestGitLabProviderDefaults(t *testing.T) {
+	p := testGitLabProvider("")
+	assert.NotEqual(t, nil, p)
+	assert.Equal(t, "GitLab", p.Data().ProviderName)
+	assert.Equal(t, "https://gitlab.com/oauth/authorize",
+		p.Data().LoginURL.String())
+	assert.Equal(t, "https://gitlab.com/oauth/token",
+		p.Data().RedeemURL.String())
+	assert.Equal(t, "https://gitlab.com/api/v3/user",
+		p.Data().ValidateURL.String())
+	assert.Equal(t, "api", p.Data().Scope)
+}
+
+func TestGitLabProviderOverrides(t *testing.T) {
+	p := NewGitLabProvider(
+		&ProviderData{
+			LoginURL: &url.URL{
+				Scheme: "https",
+				Host:   "example.com",
+				Path:   "/oauth/auth"},
+			RedeemURL: &url.URL{
+				Scheme: "https",
+				Host:   "example.com",
+				Path:   "/oauth/token"},
+			ValidateURL: &url.URL{
+				Scheme: "https",
+				Host:   "example.com",
+				Path:   "/api/v3/user"},
+			Scope: "profile"})
+	assert.NotEqual(t, nil, p)
+	assert.Equal(t, "GitLab", p.Data().ProviderName)
+	assert.Equal(t, "https://example.com/oauth/auth",
+		p.Data().LoginURL.String())
+	assert.Equal(t, "https://example.com/oauth/token",
+		p.Data().RedeemURL.String())
+	assert.Equal(t, "https://example.com/api/v3/user",
+		p.Data().ValidateURL.String())
+	assert.Equal(t, "profile", p.Data().Scope)
+}
+
+func TestGitLabProviderGetEmailAddress(t *testing.T) {
+	b := testGitLabBackend("{\"email\": \"michael.bland@gsa.gov\"}")
+	defer b.Close()
+
+	b_url, _ := url.Parse(b.URL)
+	p := testGitLabProvider(b_url.Host)
+
+	session := &SessionState{AccessToken: "imaginary_access_token"}
+	email, err := p.GetEmailAddress(session)
+	assert.Equal(t, nil, err)
+	assert.Equal(t, "michael.bland@gsa.gov", email)
+}
+
+// Note that trying to trigger the "failed building request" case is not
+// practical, since the only way it can fail is if the URL fails to parse.
+func TestGitLabProviderGetEmailAddressFailedRequest(t *testing.T) {
+	b := testGitLabBackend("unused payload")
+	defer b.Close()
+
+	b_url, _ := url.Parse(b.URL)
+	p := testGitLabProvider(b_url.Host)
+
+	// We'll trigger a request failure by using an unexpected access
+	// token. Alternatively, we could allow the parsing of the payload as
+	// JSON to fail.
+	session := &SessionState{AccessToken: "unexpected_access_token"}
+	email, err := p.GetEmailAddress(session)
+	assert.NotEqual(t, nil, err)
+	assert.Equal(t, "", email)
+}
+
+func TestGitLabProviderGetEmailAddressEmailNotPresentInPayload(t *testing.T) {
+	b := testGitLabBackend("{\"foo\": \"bar\"}")
+	defer b.Close()
+
+	b_url, _ := url.Parse(b.URL)
+	p := testGitLabProvider(b_url.Host)
+
+	session := &SessionState{AccessToken: "imaginary_access_token"}
+	email, err := p.GetEmailAddress(session)
+	assert.NotEqual(t, nil, err)
+	assert.Equal(t, "", email)
+}
diff --git a/providers/providers.go b/providers/providers.go
index db0fe13b..010e633b 100644
--- a/providers/providers.go
+++ b/providers/providers.go
@@ -26,6 +26,8 @@ func New(provider string, p *ProviderData) Provider {
 		return NewGitHubProvider(p)
 	case "azure":
 		return NewAzureProvider(p)
+	case "gitlab":
+		return NewGitLabProvider(p)
 	default:
 		return NewGoogleProvider(p)
 	}