diff --git a/Documentation/gitlab-connector.md b/Documentation/gitlab-connector.md
new file mode 100644
index 0000000000000000000000000000000000000000..fc797e9f6cd44ff1d75c7fef7f5f40279608114c
--- /dev/null
+++ b/Documentation/gitlab-connector.md
@@ -0,0 +1,29 @@
+# Authentication through Gitlab
+
+## Overview
+
+GitLab is a web-based Git repository manager with wiki and issue tracking features, using an open source license, developed by GitLab Inc. One of the login options for dex uses the GitLab OAuth2 flow to identify the end user through their GitLab account. You can use this option with [gitlab.com](gitlab.com), GitLab community or enterprise edition.
+
+When a client redeems a refresh token through dex, dex will re-query GitLab to update user information in the ID Token. To do this, __dex stores a readonly GitLab access token in its backing datastore.__ Users that reject dex's access through GitLab will also revoke all dex clients which authenticated them through GitLab.
+
+## Configuration
+
+Register a new application via `User Settings -> Applications` ensuring the callback URL is `(dex issuer)/callback`. For example if dex is listening at the non-root path `https://auth.example.com/dex` the callback would be `https://auth.example.com/dex/callback`.
+
+The following is an example of a configuration for `examples/config-dev.yaml`:
+
+```yaml
+connectors:
+  - type: gitlab
+    # Required field for connector id.
+    id: gitlab
+    # Required field for connector name.
+    name: GitLab
+    config:
+      # optional, default = https://www.gitlab.com 
+      baseURL: https://www.gitlab.com
+      # Credentials can be string literals or pulled from the environment.  
+      clientID: $GITLAB_APPLICATION_ID 
+      clientSecret: $GITLAB_CLIENT_SECRET
+      redirectURI: http://127.0.0.1:5556/dex/callback
+```
diff --git a/cmd/dex/config.go b/cmd/dex/config.go
index 74a1b091a5556ccd1e13e2f9710aca18b18f4aff..fef06c113e6241d892aec7bfe35133cbf277133a 100644
--- a/cmd/dex/config.go
+++ b/cmd/dex/config.go
@@ -11,6 +11,7 @@ import (
 	"github.com/Sirupsen/logrus"
 	"github.com/coreos/dex/connector"
 	"github.com/coreos/dex/connector/github"
+	"github.com/coreos/dex/connector/gitlab"
 	"github.com/coreos/dex/connector/ldap"
 	"github.com/coreos/dex/connector/mock"
 	"github.com/coreos/dex/connector/oidc"
@@ -182,6 +183,7 @@ var connectors = map[string]func() ConnectorConfig{
 	"mockPassword":     func() ConnectorConfig { return new(mock.PasswordConfig) },
 	"ldap":             func() ConnectorConfig { return new(ldap.Config) },
 	"github":           func() ConnectorConfig { return new(github.Config) },
+	"gitlab":           func() ConnectorConfig { return new(gitlab.Config) },
 	"oidc":             func() ConnectorConfig { return new(oidc.Config) },
 	"samlExperimental": func() ConnectorConfig { return new(saml.Config) },
 }
diff --git a/connector/gitlab/gitlab.go b/connector/gitlab/gitlab.go
new file mode 100644
index 0000000000000000000000000000000000000000..0fcc3d26ada5a784897662de791a9fe646bbe120
--- /dev/null
+++ b/connector/gitlab/gitlab.go
@@ -0,0 +1,288 @@
+// Package gitlab provides authentication strategies using Gitlab.
+package gitlab
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+	"io/ioutil"
+	"net/http"
+	"regexp"
+	"strconv"
+
+	"github.com/Sirupsen/logrus"
+	"github.com/coreos/dex/connector"
+	"golang.org/x/net/context"
+	"golang.org/x/oauth2"
+)
+
+const (
+	scopeEmail = "user:email"
+	scopeOrgs  = "read:org"
+)
+
+// Config holds configuration options for gilab logins.
+type Config struct {
+	BaseURL      string `json:"baseURL"`
+	ClientID     string `json:"clientID"`
+	ClientSecret string `json:"clientSecret"`
+	RedirectURI  string `json:"redirectURI"`
+}
+
+type gitlabUser struct {
+	ID       int
+	Name     string
+	Username string
+	State    string
+	Email    string
+	IsAdmin  bool
+}
+
+type gitlabGroup struct {
+	ID   int
+	Name string
+	Path string
+}
+
+// Open returns a strategy for logging in through GitLab.
+func (c *Config) Open(logger logrus.FieldLogger) (connector.Connector, error) {
+	if c.BaseURL == "" {
+		c.BaseURL = "https://www.gitlab.com"
+	}
+	return &gitlabConnector{
+		baseURL:      c.BaseURL,
+		redirectURI:  c.RedirectURI,
+		clientID:     c.ClientID,
+		clientSecret: c.ClientSecret,
+		logger:       logger,
+	}, nil
+}
+
+type connectorData struct {
+	// GitLab's OAuth2 tokens never expire. We don't need a refresh token.
+	AccessToken string `json:"accessToken"`
+}
+
+var (
+	_ connector.CallbackConnector = (*gitlabConnector)(nil)
+	_ connector.RefreshConnector  = (*gitlabConnector)(nil)
+)
+
+type gitlabConnector struct {
+	baseURL      string
+	redirectURI  string
+	org          string
+	clientID     string
+	clientSecret string
+	logger       logrus.FieldLogger
+}
+
+func (c *gitlabConnector) oauth2Config(scopes connector.Scopes) *oauth2.Config {
+	gitlabScopes := []string{"api"}
+	gitlabEndpoint := oauth2.Endpoint{AuthURL: c.baseURL + "/oauth/authorize", TokenURL: c.baseURL + "/oauth/token"}
+	return &oauth2.Config{
+		ClientID:     c.clientID,
+		ClientSecret: c.clientSecret,
+		Endpoint:     gitlabEndpoint,
+		Scopes:       gitlabScopes,
+		RedirectURL:  c.redirectURI,
+	}
+}
+
+func (c *gitlabConnector) LoginURL(scopes connector.Scopes, callbackURL, state string) (string, error) {
+	if c.redirectURI != callbackURL {
+		return "", fmt.Errorf("expected callback URL %q did not match the URL in the config %q", c.redirectURI, callbackURL)
+	}
+	return c.oauth2Config(scopes).AuthCodeURL(state), nil
+}
+
+type oauth2Error struct {
+	error            string
+	errorDescription string
+}
+
+func (e *oauth2Error) Error() string {
+	if e.errorDescription == "" {
+		return e.error
+	}
+	return e.error + ": " + e.errorDescription
+}
+
+func (c *gitlabConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
+	q := r.URL.Query()
+	if errType := q.Get("error"); errType != "" {
+		return identity, &oauth2Error{errType, q.Get("error_description")}
+	}
+
+	oauth2Config := c.oauth2Config(s)
+	ctx := r.Context()
+
+	token, err := oauth2Config.Exchange(ctx, q.Get("code"))
+	if err != nil {
+		return identity, fmt.Errorf("gitlab: failed to get token: %v", err)
+	}
+
+	client := oauth2Config.Client(ctx, token)
+
+	user, err := c.user(ctx, client)
+	if err != nil {
+		return identity, fmt.Errorf("gitlab: get user: %v", err)
+	}
+
+	username := user.Name
+	if username == "" {
+		username = user.Email
+	}
+	identity = connector.Identity{
+		UserID:        strconv.Itoa(user.ID),
+		Username:      username,
+		Email:         user.Email,
+		EmailVerified: true,
+	}
+
+	if s.Groups {
+		groups, err := c.groups(ctx, client)
+		if err != nil {
+			return identity, fmt.Errorf("gitlab: get groups: %v", err)
+		}
+		identity.Groups = groups
+	}
+
+	if s.OfflineAccess {
+		data := connectorData{AccessToken: token.AccessToken}
+		connData, err := json.Marshal(data)
+		if err != nil {
+			return identity, fmt.Errorf("marshal connector data: %v", err)
+		}
+		identity.ConnectorData = connData
+	}
+
+	return identity, nil
+}
+
+func (c *gitlabConnector) Refresh(ctx context.Context, s connector.Scopes, ident connector.Identity) (connector.Identity, error) {
+	if len(ident.ConnectorData) == 0 {
+		return ident, errors.New("no upstream access token found")
+	}
+
+	var data connectorData
+	if err := json.Unmarshal(ident.ConnectorData, &data); err != nil {
+		return ident, fmt.Errorf("gitlab: unmarshal access token: %v", err)
+	}
+
+	client := c.oauth2Config(s).Client(ctx, &oauth2.Token{AccessToken: data.AccessToken})
+	user, err := c.user(ctx, client)
+	if err != nil {
+		return ident, fmt.Errorf("gitlab: get user: %v", err)
+	}
+
+	username := user.Name
+	if username == "" {
+		username = user.Email
+	}
+	ident.Username = username
+	ident.Email = user.Email
+
+	if s.Groups {
+		groups, err := c.groups(ctx, client)
+		if err != nil {
+			return ident, fmt.Errorf("gitlab: get groups: %v", err)
+		}
+		ident.Groups = groups
+	}
+	return ident, nil
+}
+
+// user queries the GitLab API for profile information using the provided client. The HTTP
+// client is expected to be constructed by the golang.org/x/oauth2 package, which inserts
+// a bearer token as part of the request.
+func (c *gitlabConnector) user(ctx context.Context, client *http.Client) (gitlabUser, error) {
+	var u gitlabUser
+	req, err := http.NewRequest("GET", c.baseURL+"/api/v3/user", nil)
+	if err != nil {
+		return u, fmt.Errorf("gitlab: new req: %v", err)
+	}
+	req = req.WithContext(ctx)
+	resp, err := client.Do(req)
+	if err != nil {
+		return u, fmt.Errorf("gitlab: get URL %v", err)
+	}
+	defer resp.Body.Close()
+
+	if resp.StatusCode != http.StatusOK {
+		body, err := ioutil.ReadAll(resp.Body)
+		if err != nil {
+			return u, fmt.Errorf("gitlab: read body: %v", err)
+		}
+		return u, fmt.Errorf("%s: %s", resp.Status, body)
+	}
+
+	if err := json.NewDecoder(resp.Body).Decode(&u); err != nil {
+		return u, fmt.Errorf("failed to decode response: %v", err)
+	}
+	return u, nil
+}
+
+// groups queries the GitLab API for group membership.
+//
+// The HTTP passed client is expected to be constructed by the golang.org/x/oauth2 package,
+// which inserts a bearer token as part of the request.
+func (c *gitlabConnector) groups(ctx context.Context, client *http.Client) ([]string, error) {
+
+	apiURL := c.baseURL + "/api/v3/groups"
+
+	reNext := regexp.MustCompile("<(.*)>; rel=\"next\"")
+	reLast := regexp.MustCompile("<(.*)>; rel=\"last\"")
+
+	groups := []string{}
+	var gitlabGroups []gitlabGroup
+	for {
+		// 100 is the maximum number for per_page that allowed by gitlab
+		req, err := http.NewRequest("GET", apiURL, nil)
+		if err != nil {
+			return nil, fmt.Errorf("gitlab: new req: %v", err)
+		}
+		req = req.WithContext(ctx)
+		resp, err := client.Do(req)
+		if err != nil {
+			return nil, fmt.Errorf("gitlab: get groups: %v", err)
+		}
+		defer resp.Body.Close()
+
+		if resp.StatusCode != http.StatusOK {
+			body, err := ioutil.ReadAll(resp.Body)
+			if err != nil {
+				return nil, fmt.Errorf("gitlab: read body: %v", err)
+			}
+			return nil, fmt.Errorf("%s: %s", resp.Status, body)
+		}
+
+		if err := json.NewDecoder(resp.Body).Decode(&gitlabGroups); err != nil {
+			return nil, fmt.Errorf("gitlab: unmarshal groups: %v", err)
+		}
+
+		for _, group := range gitlabGroups {
+			groups = append(groups, group.Name)
+		}
+
+		link := resp.Header.Get("Link")
+
+		if len(reLast.FindStringSubmatch(link)) > 1 {
+			lastPageURL := reLast.FindStringSubmatch(link)[1]
+
+			if apiURL == lastPageURL {
+				break
+			}
+		} else {
+			break
+		}
+
+		if len(reNext.FindStringSubmatch(link)) > 1 {
+			apiURL = reNext.FindStringSubmatch(link)[1]
+		} else {
+			break
+		}
+
+	}
+	return groups, nil
+}
diff --git a/web/static/img/gitlab-icon.svg b/web/static/img/gitlab-icon.svg
new file mode 100644
index 0000000000000000000000000000000000000000..e8d408fa2edbd90cc804f331700ed47fda3844ad
--- /dev/null
+++ b/web/static/img/gitlab-icon.svg
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<svg width="500px" height="500px" viewBox="0 0 500 500" version="1.1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" xmlns:sketch="http://www.bohemiancoding.com/sketch/ns">
+    <!-- Generator: Sketch 3.2.2 (9983) - http://www.bohemiancoding.com/sketch -->
+    <title>logo-square</title>
+    <desc>Created with Sketch.</desc>
+    <defs></defs>
+    <g id="Page-1" stroke="none" stroke-width="1" fill="none" fill-rule="evenodd" sketch:type="MSPage">
+        <g id="logo-square" sketch:type="MSArtboardGroup">
+            <g id="logo-no-bg" sketch:type="MSLayerGroup" transform="translate(2.000000, 19.000000)">
+                <g id="Page-1" sketch:type="MSShapeGroup">
+                    <g id="gitlab_logo">
+                        <g id="g10" transform="translate(248.000000, 228.833300) scale(1, -1) translate(-248.000000, -228.833300) translate(0.000000, 0.333300)">
+                            <g id="g16">
+                                <g id="g18-Clipped">
+                                    <g id="g18">
+                                        <g>
+                                            <g id="Group" transform="translate(0.666658, 0.666658)">
+                                                <g id="g44" transform="translate(0.532000, 0.774933)" fill="#FC6D26">
+                                                    <path d="M491.999988,194.666662 L464.441322,279.481326 L409.82399,447.578655 C407.014656,456.226655 394.778657,456.226655 391.96799,447.578655 L337.349325,279.481326 L155.982663,279.481326 L101.362664,447.578655 C98.5533309,456.226655 86.3173312,456.226655 83.5066646,447.578655 L28.8893326,279.481326 L1.33199997,194.666662 C-1.18266664,186.930662 1.57199996,178.455996 8.1519998,173.674662 L246.665327,0.385333324 L485.179988,173.674662 C491.759988,178.455996 494.513321,186.930662 491.999988,194.666662" id="path46"></path>
+                                                </g>
+                                                <g id="g48" transform="translate(156.197863, 1.160267)" fill="#E24329">
+                                                    <path d="M90.9999977,0 L90.9999977,0 L181.683995,279.095993 L0.31599997,279.095993 L90.9999977,0 L90.9999977,0 Z" id="path50"></path>
+                                                </g>
+                                                <g id="g56" transform="translate(28.531199, 1.160800)" fill="#FC6D26">
+                                                    <path d="M218.666661,0 L127.982663,279.09466 L0.890666644,279.09466 L218.666661,0 L218.666661,0 Z" id="path58"></path>
+                                                </g>
+                                                <g id="g64" transform="translate(0.088533, 0.255867)" fill="#FCA326">
+                                                    <path d="M29.3333326,279.999993 L29.3333326,279.999993 L1.77466662,195.185328 C-0.738666648,187.449329 2.01466662,178.974662 8.59599979,174.194662 L247.109327,0.905333311 L29.3333326,279.999993 L29.3333326,279.999993 Z" id="path66"></path>
+                                                </g>
+                                                <g id="g72" transform="translate(29.421866, 280.255593)" fill="#E24329">
+                                                    <path d="M0,0 L127.091997,0 L72.4733315,168.097329 C69.6626649,176.746662 57.4266652,176.746662 54.617332,168.097329 L0,0 L0,0 Z" id="path74"></path>
+                                                </g>
+                                                <g id="g76" transform="translate(247.197860, 1.160800)" fill="#FC6D26">
+                                                    <path d="M0,0 L90.6839977,279.09466 L217.775995,279.09466 L0,0 L0,0 Z" id="path78"></path>
+                                                </g>
+                                                <g id="g80" transform="translate(246.307061, 0.255867)" fill="#FCA326">
+                                                    <path d="M218.666661,279.999993 L218.666661,279.999993 L246.225327,195.185328 C248.73866,187.449329 245.985327,178.974662 239.403994,174.194662 L0.890666644,0.905333311 L218.666661,279.999993 L218.666661,279.999993 Z" id="path82"></path>
+                                                </g>
+                                                <g id="g84" transform="translate(336.973725, 280.255593)" fill="#E24329">
+                                                    <path d="M127.999997,0 L0.907999977,0 L55.5266653,168.097329 C58.3373319,176.746662 70.5733316,176.746662 73.3826648,168.097329 L127.999997,0 L127.999997,0 Z" id="path86"></path>
+                                                </g>
+                                            </g>
+                                        </g>
+                                    </g>
+                                </g>
+                            </g>
+                        </g>
+                    </g>
+                </g>
+            </g>
+        </g>
+    </g>
+</svg>
\ No newline at end of file
diff --git a/web/static/main.css b/web/static/main.css
index f33b15ac9a1988e2f777935dce916d7e293a964a..008ccce99bf122f697f4065dd31636f1239892f8 100644
--- a/web/static/main.css
+++ b/web/static/main.css
@@ -62,6 +62,11 @@ body {
   background-image: url(../static/img/github-icon.svg);
 }
 
+.dex-btn-icon--gitlab {
+  background-image: url(../static/img/gitlab-icon.svg);
+  background-size: contain;
+}
+
 .dex-btn-icon--bitbucket {
   background-color: #205081;
   background-image: url(../static/img/bitbucket-icon.svg);