diff --git a/.golangci.yml b/.golangci.yml
index ed271f012ae8dfb6c84b975ec596eddc8feb1191..64357049ae0adb851e54a4cf4d611b36dacf7d4e 100644
--- a/.golangci.yml
+++ b/.golangci.yml
@@ -1,34 +1,45 @@
 run:
-    timeout: 2m
+  timeout: 2m
 
 linters-settings:
-    golint:
-        min-confidence: 0.1
-    goimports:
-        local-prefixes: github.com/dexidp/dex
+  golint:
+    min-confidence: 0.1
+  goimports:
+    local-prefixes: github.com/dexidp/dex
 
 linters:
-    enable-all: true
-    disable:
-        - funlen
-        - maligned
-        - wsl
+  disable-all: true
+  enable:
+  - bodyclose
+  - deadcode
+  - depguard
+  - dogsled
+  - gochecknoinits
+  - gofmt
+  - goimports
+  - golint
+  - gosimple
+  - govet
+  - ineffassign
+  - interfacer
+  - misspell
+  - nakedret
+  - staticcheck
+  - structcheck
+  - stylecheck
+  - typecheck
+  - unconvert
+  - unused
+  - varcheck
+  - whitespace
 
-        # TODO: fix me
-        - unparam
-        - golint
-        - goconst
-        - staticcheck
-        - nakedret
-        - errcheck
-        - gosec
-        - gochecknoinits
-        - gochecknoglobals
-        - prealloc
-        - scopelint
-        - lll
-        - dupl
-        - gocritic
-        - gocyclo
-        - gocognit
-        - godox
+  # TODO: fix linter errors before enabling
+  # - unparam
+  # - scopelint
+  # - gosec
+  # - gocyclo
+  # - lll
+  # - goconst
+  # - gocritic
+  # - errcheck
+  # - dupl
diff --git a/Makefile b/Makefile
index abf8655daeab1bac049d32e1c4a804d89b44b58c..b665bcfcc72513f9fa727b9adcee06cff1a4e534 100644
--- a/Makefile
+++ b/Makefile
@@ -18,7 +18,7 @@ export GOBIN=$(PWD)/bin
 LD_FLAGS="-w -X $(REPO_PATH)/version.Version=$(VERSION)"
 
 # Dependency versions
-GOLANGCI_VERSION = 1.21.0
+GOLANGCI_VERSION = 1.31.0
 
 build: bin/dex
 
diff --git a/connector/atlassiancrowd/atlassiancrowd.go b/connector/atlassiancrowd/atlassiancrowd.go
index 928e69a6db9fb433e5376e9bb4b962b2a99ccd66..dbc60a74f280a67ae50be384ca1a2993576bec6c 100644
--- a/connector/atlassiancrowd/atlassiancrowd.go
+++ b/connector/atlassiancrowd/atlassiancrowd.go
@@ -111,7 +111,7 @@ func (c *crowdConnector) Login(ctx context.Context, s connector.Scopes, username
 
 	// We want to return a different error if the user's password is incorrect vs
 	// if there was an error.
-	incorrectPass := false
+	var incorrectPass bool
 	var user crowdUser
 
 	client := c.crowdAPIClient()
diff --git a/connector/google/google.go b/connector/google/google.go
index 37b89edde849435da064b1a7db41fd78e060d224..d4c4e18a9c67e9e7bb3c22ab6a27634fc96e103a 100644
--- a/connector/google/google.go
+++ b/connector/google/google.go
@@ -13,6 +13,7 @@ import (
 	"golang.org/x/oauth2"
 	"golang.org/x/oauth2/google"
 	admin "google.golang.org/api/admin/directory/v1"
+	"google.golang.org/api/option"
 
 	"github.com/dexidp/dex/connector"
 	pkg_groups "github.com/dexidp/dex/pkg/groups"
@@ -289,7 +290,7 @@ func createDirectoryService(serviceAccountFilePath string, email string) (*admin
 	ctx := context.Background()
 	client := config.Client(ctx)
 
-	srv, err := admin.New(client)
+	srv, err := admin.NewService(ctx, option.WithHTTPClient(client))
 	if err != nil {
 		return nil, fmt.Errorf("unable to create directory service %v", err)
 	}
diff --git a/connector/ldap/ldap.go b/connector/ldap/ldap.go
index 2e12a4c9ecb25f14dbd6fa83bf7db4259c2c3353..99745b5808c7b6b9d511655093e13153b0a4e54e 100644
--- a/connector/ldap/ldap.go
+++ b/connector/ldap/ldap.go
@@ -56,6 +56,7 @@ import (
 //         nameAttr: name
 //
 
+// UserMatcher holds information about user and group matching.
 type UserMatcher struct {
 	UserAttr  string `json:"userAttr"`
 	GroupAttr string `json:"groupAttr"`
@@ -189,7 +190,7 @@ func (c *ldapConnector) userMatchers() []UserMatcher {
 	if len(c.GroupSearch.UserMatchers) > 0 && c.GroupSearch.UserMatchers[0].UserAttr != "" {
 		return c.GroupSearch.UserMatchers[:]
 	}
-	
+
 	return []UserMatcher{
 		{
 			UserAttr:  c.GroupSearch.UserAttr,
@@ -303,7 +304,7 @@ var (
 // do initializes a connection to the LDAP directory and passes it to the
 // provided function. It then performs appropriate teardown or reuse before
 // returning.
-func (c *ldapConnector) do(ctx context.Context, f func(c *ldap.Conn) error) error {
+func (c *ldapConnector) do(_ context.Context, f func(c *ldap.Conn) error) error {
 	// TODO(ericchiang): support context here
 	var (
 		conn *ldap.Conn
diff --git a/connector/saml/saml.go b/connector/saml/saml.go
index e0f3f933ab2626d50644e220962f06070b0c399e..29f5dd9875970d5ab92be402965f64e1c219bd20 100644
--- a/connector/saml/saml.go
+++ b/connector/saml/saml.go
@@ -11,6 +11,7 @@ import (
 	"fmt"
 	"io/ioutil"
 	"strings"
+	"sync"
 	"time"
 
 	"github.com/beevik/etree"
@@ -60,20 +61,9 @@ var (
 		nameIDformatTransient,
 	}
 	nameIDFormatLookup = make(map[string]string)
-)
 
-func init() {
-	suffix := func(s, sep string) string {
-		if i := strings.LastIndex(s, sep); i > 0 {
-			return s[i+1:]
-		}
-		return s
-	}
-	for _, format := range nameIDFormats {
-		nameIDFormatLookup[suffix(format, ":")] = format
-		nameIDFormatLookup[format] = format
-	}
-}
+	lookupOnce sync.Once
+)
 
 // Config represents configuration options for the SAML provider.
 type Config struct {
@@ -176,6 +166,19 @@ func (c *Config) openConnector(logger log.Logger) (*provider, error) {
 	if p.nameIDPolicyFormat == "" {
 		p.nameIDPolicyFormat = nameIDFormatPersistent
 	} else {
+		lookupOnce.Do(func() {
+			suffix := func(s, sep string) string {
+				if i := strings.LastIndex(s, sep); i > 0 {
+					return s[i+1:]
+				}
+				return s
+			}
+			for _, format := range nameIDFormats {
+				nameIDFormatLookup[suffix(format, ":")] = format
+				nameIDFormatLookup[format] = format
+			}
+		})
+
 		if format, ok := nameIDFormatLookup[p.nameIDPolicyFormat]; ok {
 			p.nameIDPolicyFormat = format
 		} else {
@@ -364,7 +367,7 @@ func (p *provider) HandlePOST(s connector.Scopes, samlResponse, inResponseTo str
 	switch {
 	case subject.NameID != nil:
 		if ident.UserID = subject.NameID.Value; ident.UserID == "" {
-			return ident, fmt.Errorf("NameID element does not contain a value")
+			return ident, fmt.Errorf("element NameID does not contain a value")
 		}
 	default:
 		return ident, fmt.Errorf("subject does not contain an NameID element")
@@ -488,7 +491,7 @@ func (p *provider) validateSubject(subject *subject, inResponseTo string) error
 
 			data := c.SubjectConfirmationData
 			if data == nil {
-				return fmt.Errorf("SubjectConfirmation contained no SubjectConfirmationData")
+				return fmt.Errorf("no SubjectConfirmationData field found in SubjectConfirmation")
 			}
 			if data.InResponseTo != inResponseTo {
 				return fmt.Errorf("expected SubjectConfirmationData InResponseTo value %q, got %q", inResponseTo, data.InResponseTo)
diff --git a/server/deviceflowhandlers_test.go b/server/deviceflowhandlers_test.go
index 5ab3ddb635045f1bf7538190bcd4e0d7f1bf5148..196fc54c680b0320d77e7c85b9e02445c42db187 100644
--- a/server/deviceflowhandlers_test.go
+++ b/server/deviceflowhandlers_test.go
@@ -546,7 +546,7 @@ func TestDeviceTokenResponse(t *testing.T) {
 				t.Errorf("Could read token response %v", err)
 			}
 			if tc.expectedResponseCode == http.StatusBadRequest || tc.expectedResponseCode == http.StatusUnauthorized {
-				expectJsonErrorResponse(tc.testName, body, tc.expectedServerResponse, t)
+				expectJSONErrorResponse(tc.testName, body, tc.expectedServerResponse, t)
 			} else if string(body) != tc.expectedServerResponse {
 				t.Errorf("Unexpected Server Response.  Expected %v got %v", tc.expectedServerResponse, string(body))
 			}
@@ -554,7 +554,7 @@ func TestDeviceTokenResponse(t *testing.T) {
 	}
 }
 
-func expectJsonErrorResponse(testCase string, body []byte, expectedError string, t *testing.T) {
+func expectJSONErrorResponse(testCase string, body []byte, expectedError string, t *testing.T) {
 	jsonMap := make(map[string]interface{})
 	err := json.Unmarshal(body, &jsonMap)
 	if err != nil {
diff --git a/server/handlers.go b/server/handlers.go
index 9c02524fb87a05a39f6e2b91cb704da49e9f2c1a..0bc6dcb4f7bff061b7299d8c9d146c3ff1c1eac1 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -274,7 +274,7 @@ func (s *Server) handleAuthorization(w http.ResponseWriter, r *http.Request) {
 		}
 	}
 
-	if err := s.templates.login(r, w, connectorInfos, r.URL.Path); err != nil {
+	if err := s.templates.login(r, w, connectorInfos); err != nil {
 		s.logger.Errorf("Server template error: %v", err)
 	}
 }
@@ -335,7 +335,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
 			}
 			http.Redirect(w, r, callbackURL, http.StatusFound)
 		case connector.PasswordConnector:
-			if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(conn), false, showBacklink, r.URL.Path); err != nil {
+			if err := s.templates.password(r, w, r.URL.String(), "", usernamePrompt(conn), false, showBacklink); err != nil {
 				s.logger.Errorf("Server template error: %v", err)
 			}
 		case connector.SAMLConnector:
@@ -383,7 +383,7 @@ func (s *Server) handleConnectorLogin(w http.ResponseWriter, r *http.Request) {
 			return
 		}
 		if !ok {
-			if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(passwordConnector), true, showBacklink, r.URL.Path); err != nil {
+			if err := s.templates.password(r, w, r.URL.String(), username, usernamePrompt(passwordConnector), true, showBacklink); err != nil {
 				s.logger.Errorf("Server template error: %v", err)
 			}
 			return
@@ -577,7 +577,7 @@ func (s *Server) handleApproval(w http.ResponseWriter, r *http.Request) {
 			s.renderError(r, w, http.StatusInternalServerError, "Failed to retrieve client.")
 			return
 		}
-		if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes, r.URL.Path); err != nil {
+		if err := s.templates.approval(r, w, authReq.ID, authReq.Claims.Username, client.Name, authReq.Scopes); err != nil {
 			s.logger.Errorf("Server template error: %v", err)
 		}
 	case http.MethodPost:
@@ -650,7 +650,7 @@ func (s *Server) sendCodeResponse(w http.ResponseWriter, r *http.Request, authRe
 			// Implicit and hybrid flows that try to use the OOB redirect URI are
 			// rejected earlier. If we got here we're using the code flow.
 			if authReq.RedirectURI == redirectURIOOB {
-				if err := s.templates.oob(r, w, code.ID, r.URL.Path); err != nil {
+				if err := s.templates.oob(r, w, code.ID); err != nil {
 					s.logger.Errorf("Server template error: %v", err)
 				}
 				return
diff --git a/server/templates.go b/server/templates.go
index c5b6253266f426ca6225806d3cf49cab18110fc7..601aaa080a1848bdaa11dd61b59abafd830fadcd 100644
--- a/server/templates.go
+++ b/server/templates.go
@@ -78,7 +78,7 @@ func dirExists(dir string) error {
 //    |  |- (theme name)
 //    |- templates
 //
-func loadWebConfig(c webConfig) (static, theme http.Handler, templates *templates, err error) {
+func loadWebConfig(c webConfig) (http.Handler, http.Handler, *templates, error) {
 	if c.theme == "" {
 		c.theme = "coreos"
 	}
@@ -106,11 +106,11 @@ func loadWebConfig(c webConfig) (static, theme http.Handler, templates *template
 		}
 	}
 
-	static = http.FileServer(http.Dir(staticDir))
-	theme = http.FileServer(http.Dir(themeDir))
+	static := http.FileServer(http.Dir(staticDir))
+	theme := http.FileServer(http.Dir(themeDir))
 
-	templates, err = loadTemplates(c, templatesDir)
-	return
+	templates, err := loadTemplates(c, templatesDir)
+	return static, theme, templates, err
 }
 
 // loadTemplates parses the expected templates from the provided directory.
@@ -219,8 +219,7 @@ func relativeURL(serverPath, reqPath, assetPath string) string {
 	server, req, asset := splitPath(serverPath), splitPath(reqPath), splitPath(assetPath)
 
 	// Remove common prefix of request path with server path
-	// nolint: ineffassign
-	server, req = stripCommonParts(server, req)
+	_, req = stripCommonParts(server, req)
 
 	// Remove common prefix of request path with asset path
 	asset, req = stripCommonParts(asset, req)
@@ -276,7 +275,7 @@ func (t *templates) deviceSuccess(r *http.Request, w http.ResponseWriter, client
 	return renderTemplate(w, t.deviceSuccessTmpl, data)
 }
 
-func (t *templates) login(r *http.Request, w http.ResponseWriter, connectors []connectorInfo, reqPath string) error {
+func (t *templates) login(r *http.Request, w http.ResponseWriter, connectors []connectorInfo) error {
 	sort.Sort(byName(connectors))
 	data := struct {
 		Connectors []connectorInfo
@@ -285,7 +284,7 @@ func (t *templates) login(r *http.Request, w http.ResponseWriter, connectors []c
 	return renderTemplate(w, t.loginTmpl, data)
 }
 
-func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, lastUsername, usernamePrompt string, lastWasInvalid, showBacklink bool, reqPath string) error {
+func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, lastUsername, usernamePrompt string, lastWasInvalid, showBacklink bool) error {
 	data := struct {
 		PostURL        string
 		BackLink       bool
@@ -297,7 +296,7 @@ func (t *templates) password(r *http.Request, w http.ResponseWriter, postURL, la
 	return renderTemplate(w, t.passwordTmpl, data)
 }
 
-func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, scopes []string, reqPath string) error {
+func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID, username, clientName string, scopes []string) error {
 	accesses := []string{}
 	for _, scope := range scopes {
 		access, ok := scopeDescriptions[scope]
@@ -316,7 +315,7 @@ func (t *templates) approval(r *http.Request, w http.ResponseWriter, authReqID,
 	return renderTemplate(w, t.approvalTmpl, data)
 }
 
-func (t *templates) oob(r *http.Request, w http.ResponseWriter, code string, reqPath string) error {
+func (t *templates) oob(r *http.Request, w http.ResponseWriter, code string) error {
 	data := struct {
 		Code    string
 		ReqPath string
@@ -332,7 +331,7 @@ func (t *templates) err(r *http.Request, w http.ResponseWriter, errCode int, err
 		ReqPath string
 	}{http.StatusText(errCode), errMsg, r.URL.Path}
 	if err := t.errorTmpl.Execute(w, data); err != nil {
-		return fmt.Errorf("Error rendering template %s: %s", t.errorTmpl.Name(), err)
+		return fmt.Errorf("rendering template %s failed: %s", t.errorTmpl.Name(), err)
 	}
 	return nil
 }
@@ -355,7 +354,7 @@ func renderTemplate(w http.ResponseWriter, tmpl *template.Template, data interfa
 			// TODO(ericchiang): replace with better internal server error.
 			http.Error(w, "Internal server error", http.StatusInternalServerError)
 		}
-		return fmt.Errorf("Error rendering template %s: %s", tmpl.Name(), err)
+		return fmt.Errorf("rendering template %s failed: %s", tmpl.Name(), err)
 	}
 	return nil
 }
diff --git a/storage/kubernetes/types.go b/storage/kubernetes/types.go
index f856a731c35e0526eb296248817ae4e2d68e8fcb..c3eb4172a8fbc61bdc2c3fa256ea9e660b51121c 100644
--- a/storage/kubernetes/types.go
+++ b/storage/kubernetes/types.go
@@ -679,7 +679,7 @@ type DeviceRequest struct {
 	Expiry       time.Time `json:"expiry"`
 }
 
-// AuthRequestList is a list of AuthRequests.
+// DeviceRequestList is a list of DeviceRequests.
 type DeviceRequestList struct {
 	k8sapi.TypeMeta `json:",inline"`
 	k8sapi.ListMeta `json:"metadata,omitempty"`
diff --git a/storage/sql/crud.go b/storage/sql/crud.go
index b74b76e10d97cfc15a4ae9f98bf011d2b7c30b2f..325756f6a3c76057113987a237a691ad916704c6 100644
--- a/storage/sql/crud.go
+++ b/storage/sql/crud.go
@@ -84,7 +84,9 @@ type scanner interface {
 	Scan(dest ...interface{}) error
 }
 
-func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error) {
+func (c *conn) GarbageCollect(now time.Time) (storage.GCResult, error) {
+	result := storage.GCResult{}
+
 	r, err := c.Exec(`delete from auth_request where expiry < $1`, now)
 	if err != nil {
 		return result, fmt.Errorf("gc auth_request: %v", err)
@@ -117,7 +119,7 @@ func (c *conn) GarbageCollect(now time.Time) (result storage.GCResult, err error
 		result.DeviceTokens = n
 	}
 
-	return
+	return result, err
 }
 
 func (c *conn) CreateAuthRequest(a storage.AuthRequest) error {
diff --git a/storage/storage.go b/storage/storage.go
index d11305e282533a09a8c376c61f6e90066b162ca7..82ce2cb123ff3a6f5a9a5616d4e3e610aed025d8 100644
--- a/storage/storage.go
+++ b/storage/storage.go
@@ -384,23 +384,24 @@ func randomString(n int) (string, error) {
 	return string(bytes), nil
 }
 
-//DeviceRequest represents an OIDC device authorization request.  It holds the state of a device request until the user
-//authenticates using their user code or the expiry time passes.
+// DeviceRequest represents an OIDC device authorization request. It holds the state of a device request until the user
+// authenticates using their user code or the expiry time passes.
 type DeviceRequest struct {
-	//The code the user will enter in a browser
+	// The code the user will enter in a browser
 	UserCode string
-	//The unique device code for device authentication
+	// The unique device code for device authentication
 	DeviceCode string
-	//The client ID the code is for
+	// The client ID the code is for
 	ClientID string
-	//The Client Secret
+	// The Client Secret
 	ClientSecret string
-	//The scopes the device requests
+	// The scopes the device requests
 	Scopes []string
-	//The expire time
+	// The expire time
 	Expiry time.Time
 }
 
+// DeviceToken is a structure which represents the actual token of an authorized device and its rotation parameters
 type DeviceToken struct {
 	DeviceCode          string
 	Status              string