Repository: dexidp/dex
Pull request #2344 (2021-12-05 19:51:52)

fix: return invalid_grant error on claiming token of another client
Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

<!--
Thank you for sending a pull request! Here are some tips for contributors:

1. Fill the description template below.
2. Sign a DCO (if you haven't already signed it).
3. Include appropriate tests (if necessary). Make sure that all CI checks passed.
4. If the Pull Request is a work in progress, make use of GitHub's "Draft PR" feature and mark it as such.
-->

#### Overview

Change the error returned. Add tests.

#### What this PR does / why we need it

According to [rfc6749](https://datatracker.ietf.org/doc/html/rfc6749#section-5.2), it is required to return the `invalid_grant` error if the refresh token from the request was issued for another client.

#### Special notes for your reviewer

It is required to pass the OIDC conformance test:
> Attempting to use refresh_token issued to client 2 with client 1: CheckErrorFromTokenEndpointResponseErrorInvalidGrant: 'error' field has unexpected value

![image](https://user-images.githubusercontent.com/32434187/144761443-78bc8ef0-36dc-4a22-99ac-bbb979e9e134.png)



#### Does this PR introduce a user-facing change?

<!--
If no, just write "NONE" in the release-note block below.
If yes, a release note is required:
Enter your extended release note in the block below. If the PR requires additional action from users switching to the new release, include the string "action required".
-->

```release-note
Return the invalid_grant error on claiming token of another client.
```



Patch:
diff --git a/.github/ISSUE_TEMPLATE/.editorconfig b/.github/ISSUE_TEMPLATE/.editorconfig
new file mode 100644
index 00000000..d2204156
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/.editorconfig
@@ -0,0 +1,2 @@
+[*.md]
+indent_size = 2
diff --git a/.github/dependabot.yaml b/.github/dependabot.yml
similarity index 100%
rename from .github/dependabot.yaml
rename to .github/dependabot.yml
diff --git a/.github/workflows/docker.yaml b/.github/workflows/docker.yaml
index ac119799..26c7a334 100644
--- a/.github/workflows/docker.yaml
+++ b/.github/workflows/docker.yaml
@@ -12,8 +12,6 @@ jobs:
   docker:
     name: Docker
     runs-on: ubuntu-latest
-    permissions:
-      packages: "write"
 
     steps:
       - name: Checkout
diff --git a/Dockerfile b/Dockerfile
index 3a8328b4..f0e8f972 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,4 +1,4 @@
-FROM golang:1.17.4-alpine3.14 AS builder
+FROM golang:1.17.2-alpine3.14 AS builder
 
 WORKDIR /usr/local/src/dex
 
@@ -20,7 +20,7 @@ COPY . .
 
 RUN make release-binary
 
-FROM alpine:3.15.0 AS gomplate
+FROM alpine:3.14.2 AS gomplate
 
 ARG TARGETOS
 ARG TARGETARCH
@@ -29,11 +29,11 @@ ARG TARGETVARIANT
 ENV GOMPLATE_VERSION=v3.9.0
 
 RUN wget -O /usr/local/bin/gomplate \
-    "https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \
-    && chmod +x /usr/local/bin/gomplate
+  "https://github.com/hairyhenderson/gomplate/releases/download/${GOMPLATE_VERSION}/gomplate_${TARGETOS:-linux}-${TARGETARCH:-amd64}${TARGETVARIANT}" \
+  && chmod +x /usr/local/bin/gomplate
 
 
-FROM alpine:3.15.0
+FROM alpine:3.14.2
 
 # Dex connectors, such as GitHub and Google logins require root certificates.
 # Proper installations should manage those certificates, but it's a bad user
diff --git a/README.md b/README.md
index b69bdc0a..6ae2a7b0 100644
--- a/README.md
+++ b/README.md
@@ -71,7 +71,6 @@ Dex implements the following connectors:
 | [SAML 2.0](https://dexidp.io/docs/connectors/saml/) | no | yes | no | stable | WARNING: Unmaintained and likely vulnerable to auth bypasses ([#1884](https://github.com/dexidp/dex/discussions/1884)) |
 | [GitLab](https://dexidp.io/docs/connectors/gitlab/) | yes | yes | yes | beta | |
 | [OpenID Connect](https://dexidp.io/docs/connectors/oidc/) | yes | yes | yes | beta | Includes Salesforce, Azure, etc. |
-| [OAuth 2.0](https://dexidp.io/docs/connectors/oauth/) | no | yes | yes | alpha | |
 | [Google](https://dexidp.io/docs/connectors/google/) | yes | yes | yes | alpha | |
 | [LinkedIn](https://dexidp.io/docs/connectors/linkedin/) | yes | no | no | beta | |
 | [Microsoft](https://dexidp.io/docs/connectors/microsoft/) | yes | yes | no | beta | |
diff --git a/api/v2/go.mod b/api/v2/go.mod
index 1f903ed0..3585bc14 100644
--- a/api/v2/go.mod
+++ b/api/v2/go.mod
@@ -3,7 +3,7 @@ module github.com/dexidp/dex/api/v2
 go 1.17
 
 require (
-	google.golang.org/grpc v1.42.0
+	google.golang.org/grpc v1.40.0
 	google.golang.org/protobuf v1.27.1
 )
 
diff --git a/api/v2/go.sum b/api/v2/go.sum
index f5e6130c..19b643b3 100644
--- a/api/v2/go.sum
+++ b/api/v2/go.sum
@@ -1,22 +1,20 @@
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
+github.com/OneOfOne/xxhash v1.2.2/go.mod h1:HSdplMjZKSmBqAxg5vPj2TmRDmfkzw+cTzAElWljhcU=
 github.com/antihax/optional v1.0.0/go.mod h1:uupD/76wgC+ih3iEmQUL+0Ugr19nfwCT1kdvxnR2qWY=
 github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU=
-github.com/cespare/xxhash/v2 v2.1.1/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
+github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
 github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
+github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
 github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
+github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04=
 github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
@@ -46,9 +44,9 @@ github.com/grpc-ecosystem/grpc-gateway v1.16.0/go.mod h1:BDjrQk3hbvj6Nolgz8mAMFb
 github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
 github.com/rogpeppe/fastuuid v1.2.0/go.mod h1:jVj6XXZzXRy/MSR5jhDC/2q6DgLz+nrA6LYCDYWNEvQ=
+github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
 github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
 github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA=
-github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 go.opentelemetry.io/proto/otlp v0.7.0/go.mod h1:PqfVotwruBrMGOCsRd/89rSnXhoiJIqeYNgFYFoEGnI=
 golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
@@ -97,8 +95,8 @@ google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQ
 google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk=
 google.golang.org/grpc v1.33.1/go.mod h1:fr5YgcSWrqhRRxogOsw7RzIpsmvOZ6IcH4kBYTpR3n0=
 google.golang.org/grpc v1.36.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU=
-google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
-google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
+google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
 google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM=
@@ -114,6 +112,5 @@ google.golang.org/protobuf v1.27.1/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQ
 gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
 gopkg.in/yaml.v2 v2.2.3/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
-gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
 honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
 honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4=
diff --git a/cmd/dex/serve.go b/cmd/dex/serve.go
index 1916f834..92068934 100644
--- a/cmd/dex/serve.go
+++ b/cmd/dex/serve.go
@@ -9,7 +9,6 @@ import (
 	"net"
 	"net/http"
 	"os"
-	"runtime"
 	"strings"
 	"syscall"
 	"time"
@@ -92,15 +91,6 @@ func runServe(options serveOptions) error {
 	if err != nil {
 		return fmt.Errorf("invalid config: %v", err)
 	}
-
-	logger.Infof(
-		"Dex Version: %s, Go Version: %s, Go OS/ARCH: %s %s",
-		version,
-		runtime.Version(),
-		runtime.GOOS,
-		runtime.GOARCH,
-	)
-
 	if c.Logger.Level != "" {
 		logger.Infof("config using log level: %s", c.Logger.Level)
 	}
diff --git a/connector/oauth/oauth.go b/connector/oauth/oauth.go
deleted file mode 100644
index e37932ad..00000000
--- a/connector/oauth/oauth.go
+++ /dev/null
@@ -1,292 +0,0 @@
-package oauth
-
-import (
-	"context"
-	"crypto/tls"
-	"crypto/x509"
-	"encoding/base64"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net"
-	"net/http"
-	"os"
-	"strings"
-	"time"
-
-	"golang.org/x/oauth2"
-
-	"github.com/dexidp/dex/connector"
-	"github.com/dexidp/dex/pkg/log"
-)
-
-type oauthConnector struct {
-	clientID             string
-	clientSecret         string
-	redirectURI          string
-	tokenURL             string
-	authorizationURL     string
-	userInfoURL          string
-	scopes               []string
-	userIDKey            string
-	userNameKey          string
-	preferredUsernameKey string
-	emailKey             string
-	emailVerifiedKey     string
-	groupsKey            string
-	httpClient           *http.Client
-	logger               log.Logger
-}
-
-type connectorData struct {
-	AccessToken string
-}
-
-type Config struct {
-	ClientID           string   `json:"clientID"`
-	ClientSecret       string   `json:"clientSecret"`
-	RedirectURI        string   `json:"redirectURI"`
-	TokenURL           string   `json:"tokenURL"`
-	AuthorizationURL   string   `json:"authorizationURL"`
-	UserInfoURL        string   `json:"userInfoURL"`
-	Scopes             []string `json:"scopes"`
-	RootCAs            []string `json:"rootCAs"`
-	InsecureSkipVerify bool     `json:"insecureSkipVerify"`
-	UserIDKey          string   `json:"userIDKey"` // defaults to "id"
-	ClaimMapping       struct {
-		UserNameKey          string `json:"userNameKey"`          // defaults to "user_name"
-		PreferredUsernameKey string `json:"preferredUsernameKey"` // defaults to "preferred_username"
-		GroupsKey            string `json:"groupsKey"`            // defaults to "groups"
-		EmailKey             string `json:"emailKey"`             // defaults to "email"
-		EmailVerifiedKey     string `json:"emailVerifiedKey"`     // defaults to "email_verified"
-	} `json:"claimMapping"`
-}
-
-func (c *Config) Open(id string, logger log.Logger) (connector.Connector, error) {
-	var err error
-
-	userIDKey := c.UserIDKey
-	if userIDKey == "" {
-		userIDKey = "id"
-	}
-
-	userNameKey := c.ClaimMapping.UserNameKey
-	if userNameKey == "" {
-		userNameKey = "user_name"
-	}
-
-	preferredUsernameKey := c.ClaimMapping.PreferredUsernameKey
-	if preferredUsernameKey == "" {
-		preferredUsernameKey = "preferred_username"
-	}
-
-	groupsKey := c.ClaimMapping.GroupsKey
-	if groupsKey == "" {
-		groupsKey = "groups"
-	}
-
-	emailKey := c.ClaimMapping.EmailKey
-	if emailKey == "" {
-		emailKey = "email"
-	}
-
-	emailVerifiedKey := c.ClaimMapping.EmailVerifiedKey
-	if emailVerifiedKey == "" {
-		emailVerifiedKey = "email_verified"
-	}
-
-	oauthConn := &oauthConnector{
-		clientID:             c.ClientID,
-		clientSecret:         c.ClientSecret,
-		tokenURL:             c.TokenURL,
-		authorizationURL:     c.AuthorizationURL,
-		userInfoURL:          c.UserInfoURL,
-		scopes:               c.Scopes,
-		redirectURI:          c.RedirectURI,
-		logger:               logger,
-		userIDKey:            userIDKey,
-		userNameKey:          userNameKey,
-		preferredUsernameKey: preferredUsernameKey,
-		groupsKey:            groupsKey,
-		emailKey:             emailKey,
-		emailVerifiedKey:     emailVerifiedKey,
-	}
-
-	oauthConn.httpClient, err = newHTTPClient(c.RootCAs, c.InsecureSkipVerify)
-	if err != nil {
-		return nil, err
-	}
-
-	return oauthConn, err
-}
-
-func newHTTPClient(rootCAs []string, insecureSkipVerify bool) (*http.Client, error) {
-	pool, err := x509.SystemCertPool()
-	if err != nil {
-		return nil, err
-	}
-
-	tlsConfig := tls.Config{RootCAs: pool, InsecureSkipVerify: insecureSkipVerify}
-	for _, rootCA := range rootCAs {
-		rootCABytes, err := os.ReadFile(rootCA)
-		if err != nil {
-			return nil, fmt.Errorf("failed to read root-ca: %v", err)
-		}
-		if !tlsConfig.RootCAs.AppendCertsFromPEM(rootCABytes) {
-			return nil, fmt.Errorf("no certs found in root CA file %q", rootCA)
-		}
-	}
-
-	return &http.Client{
-		Transport: &http.Transport{
-			TLSClientConfig: &tlsConfig,
-			Proxy:           http.ProxyFromEnvironment,
-			DialContext: (&net.Dialer{
-				Timeout:   30 * time.Second,
-				KeepAlive: 30 * time.Second,
-				DualStack: true,
-			}).DialContext,
-			MaxIdleConns:          100,
-			IdleConnTimeout:       90 * time.Second,
-			TLSHandshakeTimeout:   10 * time.Second,
-			ExpectContinueTimeout: 1 * time.Second,
-		},
-	}, nil
-}
-
-func (c *oauthConnector) 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", callbackURL, c.redirectURI)
-	}
-
-	oauth2Config := &oauth2.Config{
-		ClientID:     c.clientID,
-		ClientSecret: c.clientSecret,
-		Endpoint:     oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},
-		RedirectURL:  c.redirectURI,
-		Scopes:       c.scopes,
-	}
-
-	return oauth2Config.AuthCodeURL(state), nil
-}
-
-func (c *oauthConnector) HandleCallback(s connector.Scopes, r *http.Request) (identity connector.Identity, err error) {
-	q := r.URL.Query()
-	if errType := q.Get("error"); errType != "" {
-		return identity, errors.New(q.Get("error_description"))
-	}
-
-	oauth2Config := &oauth2.Config{
-		ClientID:     c.clientID,
-		ClientSecret: c.clientSecret,
-		Endpoint:     oauth2.Endpoint{TokenURL: c.tokenURL, AuthURL: c.authorizationURL},
-		RedirectURL:  c.redirectURI,
-		Scopes:       c.scopes,
-	}
-
-	ctx := context.WithValue(r.Context(), oauth2.HTTPClient, c.httpClient)
-
-	token, err := oauth2Config.Exchange(ctx, q.Get("code"))
-	if err != nil {
-		return identity, fmt.Errorf("OAuth connector: failed to get token: %v", err)
-	}
-
-	client := oauth2.NewClient(ctx, oauth2.StaticTokenSource(token))
-
-	userInfoResp, err := client.Get(c.userInfoURL)
-	if err != nil {
-		return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: %v", err)
-	}
-	defer userInfoResp.Body.Close()
-
-	if userInfoResp.StatusCode != http.StatusOK {
-		return identity, fmt.Errorf("OAuth Connector: failed to execute request to userinfo: status %d", userInfoResp.StatusCode)
-	}
-
-	var userInfoResult map[string]interface{}
-	err = json.NewDecoder(userInfoResp.Body).Decode(&userInfoResult)
-	if err != nil {
-		return identity, fmt.Errorf("OAuth Connector: failed to parse userinfo: %v", err)
-	}
-
-	userID, found := userInfoResult[c.userIDKey].(string)
-	if !found {
-		return identity, fmt.Errorf("OAuth Connector: not found %v claim", c.userIDKey)
-	}
-
-	identity.UserID = userID
-	identity.Username, _ = userInfoResult[c.userNameKey].(string)
-	identity.PreferredUsername, _ = userInfoResult[c.preferredUsernameKey].(string)
-	identity.Email, _ = userInfoResult[c.emailKey].(string)
-	identity.EmailVerified, _ = userInfoResult[c.emailVerifiedKey].(bool)
-
-	if s.Groups {
-		groups := map[string]struct{}{}
-
-		c.addGroupsFromMap(groups, userInfoResult)
-		c.addGroupsFromToken(groups, token.AccessToken)
-
-		for groupName := range groups {
-			identity.Groups = append(identity.Groups, groupName)
-		}
-	}
-
-	if s.OfflineAccess {
-		data := connectorData{AccessToken: token.AccessToken}
-		connData, err := json.Marshal(data)
-		if err != nil {
-			return identity, fmt.Errorf("OAuth Connector: failed to parse connector data for offline access: %v", err)
-		}
-		identity.ConnectorData = connData
-	}
-
-	return identity, nil
-}
-
-func (c *oauthConnector) addGroupsFromMap(groups map[string]struct{}, result map[string]interface{}) error {
-	groupsClaim, ok := result[c.groupsKey].([]interface{})
-	if !ok {
-		return errors.New("cannot convert to slice")
-	}
-
-	for _, group := range groupsClaim {
-		if groupString, ok := group.(string); ok {
-			groups[groupString] = struct{}{}
-		}
-		if groupMap, ok := group.(map[string]interface{}); ok {
-			if groupName, ok := groupMap["name"].(string); ok {
-				groups[groupName] = struct{}{}
-			}
-		}
-	}
-
-	return nil
-}
-
-func (c *oauthConnector) addGroupsFromToken(groups map[string]struct{}, token string) error {
-	parts := strings.Split(token, ".")
-	if len(parts) < 2 {
-		return errors.New("invalid token")
-	}
-
-	decoded, err := decode(parts[1])
-	if err != nil {
-		return err
-	}
-
-	var claimsMap map[string]interface{}
-	err = json.Unmarshal(decoded, &claimsMap)
-	if err != nil {
-		return err
-	}
-
-	return c.addGroupsFromMap(groups, claimsMap)
-}
-
-func decode(seg string) ([]byte, error) {
-	if l := len(seg) % 4; l > 0 {
-		seg += strings.Repeat("=", 4-l)
-	}
-
-	return base64.URLEncoding.DecodeString(seg)
-}
diff --git a/go.mod b/go.mod
index ee9bf202..895f6d4a 100644
--- a/go.mod
+++ b/go.mod
@@ -18,9 +18,9 @@ require (
 	github.com/gorilla/mux v1.8.0
 	github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0
 	github.com/kylelemons/godebug v1.1.0
-	github.com/lib/pq v1.10.4
+	github.com/lib/pq v1.10.3
 	github.com/mattermost/xml-roundtrip-validator v0.1.0
-	github.com/mattn/go-sqlite3 v1.14.9
+	github.com/mattn/go-sqlite3 v1.14.8
 	github.com/oklog/run v1.1.0
 	github.com/pkg/errors v0.9.1
 	github.com/prometheus/client_golang v1.11.0
@@ -32,15 +32,15 @@ require (
 	go.etcd.io/etcd/client/v3 v3.5.1
 	golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2
 	golang.org/x/net v0.0.0-20210503060351-7fd8e65b6420
-	golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8
-	google.golang.org/api v0.61.0
-	google.golang.org/grpc v1.42.0
+	golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f
+	google.golang.org/api v0.57.0
+	google.golang.org/grpc v1.40.0
 	google.golang.org/protobuf v1.27.1
 	gopkg.in/square/go-jose.v2 v2.6.0
 )
 
 require (
-	cloud.google.com/go v0.97.0 // indirect
+	cloud.google.com/go v0.94.1 // indirect
 	github.com/Azure/go-ntlmssp v0.0.0-20200615164410-66371956d46c // indirect
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver/v3 v3.1.1 // indirect
@@ -54,7 +54,7 @@ require (
 	github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect
 	github.com/golang/protobuf v1.5.2 // indirect
 	github.com/google/uuid v1.3.0 // indirect
-	github.com/googleapis/gax-go/v2 v2.1.1 // indirect
+	github.com/googleapis/gax-go/v2 v2.1.0 // indirect
 	github.com/huandu/xstrings v1.3.1 // indirect
 	github.com/imdario/mergo v0.3.11 // indirect
 	github.com/inconshreveable/mousetrap v1.0.0 // indirect
@@ -74,10 +74,10 @@ require (
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
 	go.uber.org/zap v1.17.0 // indirect
-	golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 // indirect
+	golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 // indirect
 	golang.org/x/text v0.3.6 // indirect
 	google.golang.org/appengine v1.6.7 // indirect
-	google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 // indirect
+	google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 // indirect
 	gopkg.in/yaml.v2 v2.4.0 // indirect
 	gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b // indirect
 )
diff --git a/go.sum b/go.sum
index 9e1d38f3..702e584b 100644
--- a/go.sum
+++ b/go.sum
@@ -23,9 +23,8 @@ cloud.google.com/go v0.84.0/go.mod h1:RazrYuxIK6Kb7YrzzhPoLmCVzl7Sup4NrbKPg8KHSU
 cloud.google.com/go v0.87.0/go.mod h1:TpDYlFy7vuLzZMMZ+B6iRiELaY7z/gJPaqbMx6mlWcY=
 cloud.google.com/go v0.90.0/go.mod h1:kRX0mNRHe0e2rC6oNakvwQqzyDmg57xJ+SZU1eT2aDQ=
 cloud.google.com/go v0.93.3/go.mod h1:8utlLll2EF5XMAV15woO4lSbWQlk8rer9aLOfLh7+YI=
+cloud.google.com/go v0.94.1 h1:DwuSvDZ1pTYGbXo8yOJevCTr3BoBlE+OVkHAKiYQUXc=
 cloud.google.com/go v0.94.1/go.mod h1:qAlAugsXlC+JWO+Bke5vCtc9ONxjQT3drlTTnAplMW4=
-cloud.google.com/go v0.97.0 h1:3DXvAyifywvq64LfkKaMOmkWPS1CikIQdMe2lY9vxU8=
-cloud.google.com/go v0.97.0/go.mod h1:GF7l59pYBVlXQIBLx3a761cZ41F9bBH3JUlihCt2Udc=
 cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o=
 cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE=
 cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc=
@@ -94,11 +93,7 @@ github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDk
 github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc=
 github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
 github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk=
-github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4/go.mod h1:6pvJx4me5XPnfI9Z40ddWsdw2W/uZgQLFXToKeRcDiI=
 github.com/cncf/xds/go v0.0.0-20210312221358-fbca930ec8ed/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210805033703-aa0b78936158/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20210922020428-25de7278fc84/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
-github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1/go.mod h1:eXthEFrGJvWHgFFCl3hGmgk+/aYT6PnTQLykKQRLhEs=
 github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
 github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
 github.com/coreos/go-oidc/v3 v3.1.0 h1:6avEvcdvTa1qYsOZ6I5PRkSYHzpTNWgKYmaJfaYbrRw=
@@ -125,7 +120,6 @@ github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5y
 github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210217033140-668b12f5399d/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk=
 github.com/envoyproxy/go-control-plane v0.9.9-0.20210512163311-63b5d3c536b0/go.mod h1:hliV/p42l8fGbc6Y9bQ70uLwIvmJyVE5k4iMKlh8wCQ=
-github.com/envoyproxy/go-control-plane v0.9.10-0.20210907150352-cf90f659a021/go.mod h1:AFq3mo9L8Lqqiid3OhADV3RfLJnjiw63cSpi+fDTRC0=
 github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c=
 github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4=
 github.com/felixge/httpsnoop v1.0.1/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U=
@@ -236,9 +230,8 @@ github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
 github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg=
 github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk=
+github.com/googleapis/gax-go/v2 v2.1.0 h1:6DWmvNpomjL1+3liNSZbVns3zsYzzCjm6pRBO1tLeso=
 github.com/googleapis/gax-go/v2 v2.1.0/go.mod h1:Q3nei7sK6ybPYH7twZdmQpAd1MKb7pfu6SK+H1/DsU0=
-github.com/googleapis/gax-go/v2 v2.1.1 h1:dp3bWCh+PPO1zjRRiCSczJav13sBvG4UhNyVTa1KqdU=
-github.com/googleapis/gax-go/v2 v2.1.1/go.mod h1:hddJymUZASv3XPyGkUpKj8pPO47Rmb0eJc8R6ouapiM=
 github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
 github.com/gorilla/handlers v1.5.1 h1:9lRY6j8DEeeBT10CvO9hGW0gmky0BprnvDI5vfhUHH4=
 github.com/gorilla/handlers v1.5.1/go.mod h1:t8XrUpc4KVXb7HGyJ4/cEnwQiaxrX/hz1Zv/4g96P1Q=
@@ -310,8 +303,8 @@ github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 github.com/kylelemons/godebug v1.1.0 h1:RPNrshWIDI6G2gRW9EHilWtl7Z6Sb1BR0xunSBf0SNc=
 github.com/kylelemons/godebug v1.1.0/go.mod h1:9/0rRGxNHcop5bhtWyNeEfOS8JIWk580+fNqagV/RAw=
 github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
-github.com/lib/pq v1.10.4 h1:SO9z7FRPzA03QhHKJrH5BXA6HU1rS4V2nIVrrNC1iYk=
-github.com/lib/pq v1.10.4/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
+github.com/lib/pq v1.10.3 h1:v9QZf2Sn6AmjXtQeFpdoq/eaNtYP6IN+7lcrygsIAtg=
+github.com/lib/pq v1.10.3/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o=
 github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
 github.com/magiconair/properties v1.8.5/go.mod h1:y3VJvCyxH9uVvJTWEGAELF3aiYNyPKd5NZ3oSwXrF60=
 github.com/mattermost/xml-roundtrip-validator v0.1.0 h1:RXbVD2UAl7A7nOTR4u7E3ILa4IbtvKBHw64LDsmu9hU=
@@ -319,9 +312,8 @@ github.com/mattermost/xml-roundtrip-validator v0.1.0/go.mod h1:qccnGMcpgwcNaBnxq
 github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
 github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
 github.com/mattn/go-runewidth v0.0.9/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
+github.com/mattn/go-sqlite3 v1.14.8 h1:gDp86IdQsN/xWjIEmr9MF6o9mpksUgh0fu+9ByFxzIU=
 github.com/mattn/go-sqlite3 v1.14.8/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
-github.com/mattn/go-sqlite3 v1.14.9 h1:10HX2Td0ocZpYEjhilsuo6WWtUqttj2Kb0KtD86/KYA=
-github.com/mattn/go-sqlite3 v1.14.9/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1 h1:4hp9jkHxhMHkqkrB3Ix0jegS5sx/RkqARlsWZ6pIwiU=
 github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
 github.com/miekg/dns v1.0.14/go.mod h1:W1PPwlIAgtquWBMBEV9nkV9Cazfe8ScdGz/Lj7v3Nrg=
@@ -573,9 +565,8 @@ golang.org/x/oauth2 v0.0.0-20210402161424-2e8d93401602/go.mod h1:KelEdhl1UZF7XfJ
 golang.org/x/oauth2 v0.0.0-20210514164344-f6687ab2804c/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210628180205-a41e5a781914/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/oauth2 v0.0.0-20210805134026-6f1e6394065a/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
+golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f h1:Qmd2pbz05z7z6lm0DrgQVVPuBm92jqujBKMHMOlOQEw=
 golang.org/x/oauth2 v0.0.0-20210819190943-2bc19b11175f/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8 h1:RerP+noqYHUQ8CMRcPlC2nvTa4dcBIjegkuWdcUDuqg=
-golang.org/x/oauth2 v0.0.0-20211104180415-d3ed0bb246c8/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A=
 golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
 golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
@@ -645,9 +636,8 @@ golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBc
 golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20210823070655-63515b42dcdf/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365 h1:6wSTsvPddg9gc/mVEEyk9oOAoxn+bT4Z9q1zx+4RwA4=
 golang.org/x/sys v0.0.0-20210908233432-aa78b53d3365/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.0.0-20211124211545-fe61309f8881 h1:TyHqChC80pFkXWraUUf6RuB5IqFdQieMLwwCJokV2pc=
-golang.org/x/sys v0.0.0-20211124211545-fe61309f8881/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
@@ -751,10 +741,8 @@ google.golang.org/api v0.50.0/go.mod h1:4bNT5pAuq5ji4SRZm+5QIkjny9JAyVD/3gaSihNe
 google.golang.org/api v0.51.0/go.mod h1:t4HdrdoNgyN5cbEfm7Lum0lcLDLiise1F8qDKX00sOU=
 google.golang.org/api v0.54.0/go.mod h1:7C4bFFOvVDGXjfDTAsgGwDgAxRDeQ4X8NvUedIt6z3k=
 google.golang.org/api v0.55.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
-google.golang.org/api v0.56.0/go.mod h1:38yMfeP1kfjsl8isn0tliTjIb1rJXcQi4UXlbqivdVE=
+google.golang.org/api v0.57.0 h1:4t9zuDlHLcIx0ZEhmXEeFVCRsiOgpgn2QOH9N0MNjPI=
 google.golang.org/api v0.57.0/go.mod h1:dVPlbZyBo2/OjBpmvNdpn2GRm6rPy75jyU7bmhdrMgI=
-google.golang.org/api v0.61.0 h1:TXXKS1slM3b2bZNJwD5DV/Tp6/M2cLzLOLh9PjDhrw8=
-google.golang.org/api v0.61.0/go.mod h1:xQRti5UdCmoCEqFxcz93fTl338AVqDgyaDRuOZ3hg9I=
 google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM=
 google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
 google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4=
@@ -816,11 +804,8 @@ google.golang.org/genproto v0.0.0-20210813162853-db860fec028c/go.mod h1:cFeNkxwy
 google.golang.org/genproto v0.0.0-20210821163610-241b8fcbd6c8/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210828152312-66f60bf46e71/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
 google.golang.org/genproto v0.0.0-20210831024726-fe130286e0e2/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
+google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83 h1:3V2dxSZpz4zozWWUq36vUxXEKnSYitEH2LdsAx+RUmg=
 google.golang.org/genproto v0.0.0-20210903162649-d08c68adba83/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210909211513-a8c4777a87af/go.mod h1:eFjDcFEctNawg4eG61bRv87N7iHBWyVhJu7u1kqDUXY=
-google.golang.org/genproto v0.0.0-20210924002016-3dee208752a0/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
-google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1 h1:b9mVrqYfq3P4bCdaLg1qtBnPzUYgglsIdjZkL/fQVOE=
-google.golang.org/genproto v0.0.0-20211118181313-81c1377c94b1/go.mod h1:5CzLGKJ67TSI2B9POpiiyGha0AjJvZIUgRMt1dSmuhc=
 google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c=
 google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38=
 google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM=
@@ -845,9 +830,8 @@ google.golang.org/grpc v1.37.1/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQ
 google.golang.org/grpc v1.38.0/go.mod h1:NREThFqKR1f3iQ6oBuvc5LadQuXVGo9rkm5ZGrQdJfM=
 google.golang.org/grpc v1.39.0/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
 google.golang.org/grpc v1.39.1/go.mod h1:PImNr+rS9TWYb2O4/emRugxiyHZ5JyHW5F+RPnDzfrE=
+google.golang.org/grpc v1.40.0 h1:AGJ0Ih4mHjSeibYkFGh1dD9KJ/eOtZ93I6hoHhukQ5Q=
 google.golang.org/grpc v1.40.0/go.mod h1:ogyxbiOoUXAkP+4+xa6PZSE9DZgIHtSpzjDTB9KAK34=
-google.golang.org/grpc v1.42.0 h1:XT2/MFpuPFsEX2fWh3YQtHkZ+WYZFQRfaUgLZYj/p6A=
-google.golang.org/grpc v1.42.0/go.mod h1:k+4IHHFw41K8+bbowsex27ge2rCb65oeWqe4jJ590SU=
 google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.1.0/go.mod h1:6Kw0yEErY5E/yWrBtf03jp27GLLJujG4z/JK95pnjjw=
 google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8=
 google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0=
diff --git a/server/handlers.go b/server/handlers.go
index acbf4ecb..37a1f9ac 100755
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -1106,17 +1106,10 @@ func (s *Server) handlePasswordGrant(w http.ResponseWriter, r *http.Request, cli
 		Groups:            identity.Groups,
 	}
 
-	accessToken, err := s.newAccessToken(client.ID, claims, scopes, nonce, connID)
-	if err != nil {
-		s.logger.Errorf("password grant failed to create new access token: %v", err)
-		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
-		return
-	}
-
+	accessToken := storage.NewID()
 	idToken, expiry, err := s.newIDToken(client.ID, claims, scopes, nonce, accessToken, "", connID)
 	if err != nil {
-		s.logger.Errorf("password grant failed to create new ID token: %v", err)
-		s.tokenErrHelper(w, errServerError, "", http.StatusInternalServerError)
+		s.tokenErrHelper(w, errServerError, fmt.Sprintf("failed to create ID token: %v", err), http.StatusInternalServerError)
 		return
 	}
 
diff --git a/server/refreshhandlers.go b/server/refreshhandlers.go
index 6a638fb0..d8c912c9 100644
--- a/server/refreshhandlers.go
+++ b/server/refreshhandlers.go
@@ -76,7 +76,9 @@ func (s *Server) getRefreshTokenFromStorage(clientID string, token *internal.Ref
 
 	if refresh.ClientID != clientID {
 		s.logger.Errorf("client %s trying to claim token for client %s", clientID, refresh.ClientID)
-		return nil, invalidErr
+		// According to https://datatracker.ietf.org/doc/html/rfc6749#section-5.2 Dex should respond with an
+		//  invalid grant error if token has already been claimed by another client.
+		return nil, &refreshError{msg: errInvalidGrant, desc: invalidErr.desc, code: http.StatusBadRequest}
 	}
 
 	if refresh.Token != token.Token {
@@ -227,13 +229,16 @@ func (s *Server) updateRefreshToken(token *internal.RefreshToken, refresh *stora
 
 	lastUsed := s.now()
 
+	rerr := s.updateOfflineSession(refresh, ident, lastUsed)
+	if rerr != nil {
+		return nil, rerr
+	}
+
 	refreshTokenUpdater := func(old storage.RefreshToken) (storage.RefreshToken, error) {
 		if s.refreshTokenPolicy.RotationEnabled() {
 			if old.Token != token.Token {
 				if s.refreshTokenPolicy.AllowedToReuse(old.LastUsed) && old.ObsoleteToken == token.Token {
 					newToken.Token = old.Token
-					// Do not update last used time for offline session if token is allowed to be reused
-					lastUsed = old.LastUsed
 					return old, nil
 				}
 				return old, errors.New("refresh token claimed twice")
@@ -265,11 +270,6 @@ func (s *Server) updateRefreshToken(token *internal.RefreshToken, refresh *stora
 		return nil, newInternalServerError()
 	}
 
-	rerr := s.updateOfflineSession(refresh, ident, lastUsed)
-	if rerr != nil {
-		return nil, rerr
-	}
-
 	return newToken, nil
 }
 
diff --git a/server/server.go b/server/server.go
index 6b653fdb..ecd6c935 100755
--- a/server/server.go
+++ b/server/server.go
@@ -38,7 +38,6 @@ import (
 	"github.com/dexidp/dex/connector/linkedin"
 	"github.com/dexidp/dex/connector/microsoft"
 	"github.com/dexidp/dex/connector/mock"
-	"github.com/dexidp/dex/connector/oauth"
 	"github.com/dexidp/dex/connector/oidc"
 	"github.com/dexidp/dex/connector/openshift"
 	"github.com/dexidp/dex/connector/saml"
@@ -539,7 +538,6 @@ var ConnectorsConfig = map[string]func() ConnectorConfig{
 	"gitlab":          func() ConnectorConfig { return new(gitlab.Config) },
 	"google":          func() ConnectorConfig { return new(google.Config) },
 	"oidc":            func() ConnectorConfig { return new(oidc.Config) },
-	"oauth":           func() ConnectorConfig { return new(oauth.Config) },
 	"saml":            func() ConnectorConfig { return new(saml.Config) },
 	"authproxy":       func() ConnectorConfig { return new(authproxy.Config) },
 	"linkedin":        func() ConnectorConfig { return new(linkedin.Config) },
diff --git a/server/templates.go b/server/templates.go
index 9be8019a..e46855b1 100644
--- a/server/templates.go
+++ b/server/templates.go
@@ -8,6 +8,7 @@ import (
 	"net/http"
 	"net/url"
 	"path"
+	"path/filepath"
 	"sort"
 	"strings"
 
@@ -108,7 +109,7 @@ func loadWebConfig(c webConfig) (http.Handler, http.Handler, *templates, error)
 	if err != nil {
 		return nil, nil, nil, fmt.Errorf("read static dir: %v", err)
 	}
-	themeFiles, err := fs.Sub(c.webFS, path.Join("themes", c.theme))
+	themeFiles, err := fs.Sub(c.webFS, filepath.Join("themes", c.theme))
 	if err != nil {
 		return nil, nil, nil, fmt.Errorf("read themes dir: %v", err)
 	}
@@ -132,7 +133,7 @@ func loadTemplates(c webConfig, templatesDir string) (*templates, error) {
 		if file.IsDir() {
 			continue
 		}
-		filenames = append(filenames, path.Join(templatesDir, file.Name()))
+		filenames = append(filenames, filepath.Join(templatesDir, file.Name()))
 	}
 	if len(filenames) == 0 {
 		return nil, fmt.Errorf("no files in template dir %q", templatesDir)
diff --git a/storage/kubernetes/storage.go b/storage/kubernetes/storage.go
index ca505859..13549ef5 100644
--- a/storage/kubernetes/storage.go
+++ b/storage/kubernetes/storage.go
@@ -740,14 +740,13 @@ func retryOnConflict(ctx context.Context, action func() error) error {
 	for {
 		select {
 		case <-time.After(getNextStep()):
-			err := action()
-			if err == nil || !isKubernetesAPIConflictError(err) {
+			if err := action(); err == nil || !isKubernetesAPIConflictError(err) {
 				return err
 			}
 
 			attempts++
 			if attempts >= 4 {
-				return fmt.Errorf("maximum timeout reached while retrying a conflicted request: %w", err)
+				return errors.New("maximum timeout reached while retrying a conflicted request")
 			}
 		case <-ctx.Done():
 			return errors.New("canceled")


Test patch:
diff --git a/connector/oauth/oauth_test.go b/connector/oauth/oauth_test.go
deleted file mode 100644
index 082a3aa5..00000000
--- a/connector/oauth/oauth_test.go
+++ /dev/null
@@ -1,271 +0,0 @@
-package oauth
-
-import (
-	"crypto/rand"
-	"crypto/rsa"
-	"encoding/json"
-	"errors"
-	"fmt"
-	"net/http"
-	"net/http/httptest"
-	"net/url"
-	"sort"
-	"testing"
-
-	"github.com/sirupsen/logrus"
-	"github.com/stretchr/testify/assert"
-	jose "gopkg.in/square/go-jose.v2"
-
-	"github.com/dexidp/dex/connector"
-)
-
-func TestOpen(t *testing.T) {
-	tokenClaims := map[string]interface{}{}
-	userInfoClaims := map[string]interface{}{}
-
-	testServer := testSetup(t, tokenClaims, userInfoClaims)
-	defer testServer.Close()
-
-	conn := newConnector(t, testServer.URL)
-
-	sort.Strings(conn.scopes)
-
-	assert.Equal(t, conn.clientID, "testClient")
-	assert.Equal(t, conn.clientSecret, "testSecret")
-	assert.Equal(t, conn.redirectURI, testServer.URL+"/callback")
-	assert.Equal(t, conn.tokenURL, testServer.URL+"/token")
-	assert.Equal(t, conn.authorizationURL, testServer.URL+"/authorize")
-	assert.Equal(t, conn.userInfoURL, testServer.URL+"/userinfo")
-	assert.Equal(t, len(conn.scopes), 2)
-	assert.Equal(t, conn.scopes[0], "groups")
-	assert.Equal(t, conn.scopes[1], "openid")
-}
-
-func TestLoginURL(t *testing.T) {
-	tokenClaims := map[string]interface{}{}
-	userInfoClaims := map[string]interface{}{}
-
-	testServer := testSetup(t, tokenClaims, userInfoClaims)
-	defer testServer.Close()
-
-	conn := newConnector(t, testServer.URL)
-
-	loginURL, err := conn.LoginURL(connector.Scopes{}, conn.redirectURI, "some-state")
-	assert.Equal(t, err, nil)
-
-	expectedURL, err := url.Parse(testServer.URL + "/authorize")
-	assert.Equal(t, err, nil)
-
-	values := url.Values{}
-	values.Add("client_id", "testClient")
-	values.Add("redirect_uri", conn.redirectURI)
-	values.Add("response_type", "code")
-	values.Add("scope", "openid groups")
-	values.Add("state", "some-state")
-	expectedURL.RawQuery = values.Encode()
-
-	assert.Equal(t, loginURL, expectedURL.String())
-}
-
-func TestHandleCallBackForGroupsInUserInfo(t *testing.T) {
-	tokenClaims := map[string]interface{}{}
-
-	userInfoClaims := map[string]interface{}{
-		"name":               "test-name",
-		"user_id_key":        "test-user-id",
-		"user_name_key":      "test-username",
-		"preferred_username": "test-preferred-username",
-		"mail":               "mod_mail",
-		"has_verified_email": false,
-		"groups_key":         []string{"admin-group", "user-group"},
-	}
-
-	testServer := testSetup(t, tokenClaims, userInfoClaims)
-	defer testServer.Close()
-
-	conn := newConnector(t, testServer.URL)
-	req := newRequestWithAuthCode(t, testServer.URL, "some-code")
-
-	identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
-	assert.Equal(t, err, nil)
-
-	sort.Strings(identity.Groups)
-	assert.Equal(t, len(identity.Groups), 2)
-	assert.Equal(t, identity.Groups[0], "admin-group")
-	assert.Equal(t, identity.Groups[1], "user-group")
-	assert.Equal(t, identity.UserID, "test-user-id")
-	assert.Equal(t, identity.Username, "test-username")
-	assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
-	assert.Equal(t, identity.Email, "mod_mail")
-	assert.Equal(t, identity.EmailVerified, false)
-}
-
-func TestHandleCallBackForGroupMapsInUserInfo(t *testing.T) {
-	tokenClaims := map[string]interface{}{}
-
-	userInfoClaims := map[string]interface{}{
-		"name":               "test-name",
-		"user_id_key":        "test-user-id",
-		"user_name_key":      "test-username",
-		"preferred_username": "test-preferred-username",
-		"mail":               "mod_mail",
-		"has_verified_email": false,
-		"groups_key": []interface{}{
-			map[string]string{"name": "admin-group", "id": "111"},
-			map[string]string{"name": "user-group", "id": "222"},
-		},
-	}
-
-	testServer := testSetup(t, tokenClaims, userInfoClaims)
-	defer testServer.Close()
-
-	conn := newConnector(t, testServer.URL)
-	req := newRequestWithAuthCode(t, testServer.URL, "some-code")
-
-	identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
-	assert.Equal(t, err, nil)
-
-	sort.Strings(identity.Groups)
-	assert.Equal(t, len(identity.Groups), 2)
-	assert.Equal(t, identity.Groups[0], "admin-group")
-	assert.Equal(t, identity.Groups[1], "user-group")
-	assert.Equal(t, identity.UserID, "test-user-id")
-	assert.Equal(t, identity.Username, "test-username")
-	assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
-	assert.Equal(t, identity.Email, "mod_mail")
-	assert.Equal(t, identity.EmailVerified, false)
-}
-
-func TestHandleCallBackForGroupsInToken(t *testing.T) {
-	tokenClaims := map[string]interface{}{
-		"groups_key": []string{"test-group"},
-	}
-
-	userInfoClaims := map[string]interface{}{
-		"name":               "test-name",
-		"user_id_key":        "test-user-id",
-		"user_name_key":      "test-username",
-		"preferred_username": "test-preferred-username",
-		"email":              "test-email",
-		"email_verified":     true,
-	}
-
-	testServer := testSetup(t, tokenClaims, userInfoClaims)
-	defer testServer.Close()
-
-	conn := newConnector(t, testServer.URL)
-	req := newRequestWithAuthCode(t, testServer.URL, "some-code")
-
-	identity, err := conn.HandleCallback(connector.Scopes{Groups: true}, req)
-	assert.Equal(t, err, nil)
-
-	assert.Equal(t, len(identity.Groups), 1)
-	assert.Equal(t, identity.Groups[0], "test-group")
-	assert.Equal(t, identity.PreferredUsername, "test-preferred-username")
-	assert.Equal(t, identity.UserID, "test-user-id")
-	assert.Equal(t, identity.Username, "test-username")
-	assert.Equal(t, identity.Email, "")
-	assert.Equal(t, identity.EmailVerified, false)
-}
-
-func testSetup(t *testing.T, tokenClaims map[string]interface{}, userInfoClaims map[string]interface{}) *httptest.Server {
-	key, err := rsa.GenerateKey(rand.Reader, 1024)
-	if err != nil {
-		t.Fatal("Failed to generate rsa key", err)
-	}
-
-	jwk := jose.JSONWebKey{
-		Key:       key,
-		KeyID:     "some-key",
-		Algorithm: "RSA",
-	}
-
-	mux := http.NewServeMux()
-
-	mux.HandleFunc("/token", func(w http.ResponseWriter, r *http.Request) {
-		token, err := newToken(&jwk, tokenClaims)
-		if err != nil {
-			t.Fatal("unable to generate token", err)
-		}
-
-		w.Header().Add("Content-Type", "application/json")
-		json.NewEncoder(w).Encode(&map[string]string{
-			"access_token": token,
-			"id_token":     token,
-			"token_type":   "Bearer",
-		})
-	})
-
-	mux.HandleFunc("/userinfo", func(w http.ResponseWriter, r *http.Request) {
-		w.Header().Add("Content-Type", "application/json")
-		json.NewEncoder(w).Encode(userInfoClaims)
-	})
-
-	return httptest.NewServer(mux)
-}
-
-func newToken(key *jose.JSONWebKey, claims map[string]interface{}) (string, error) {
-	signingKey := jose.SigningKey{Key: key, Algorithm: jose.RS256}
-
-	signer, err := jose.NewSigner(signingKey, &jose.SignerOptions{})
-	if err != nil {
-		return "", fmt.Errorf("new signer: %v", err)
-	}
-
-	payload, err := json.Marshal(claims)
-	if err != nil {
-		return "", fmt.Errorf("marshaling claims: %v", err)
-	}
-
-	signature, err := signer.Sign(payload)
-	if err != nil {
-		return "", fmt.Errorf("signing payload: %v", err)
-	}
-
-	return signature.CompactSerialize()
-}
-
-func newConnector(t *testing.T, serverURL string) *oauthConnector {
-	testConfig := Config{
-		ClientID:         "testClient",
-		ClientSecret:     "testSecret",
-		RedirectURI:      serverURL + "/callback",
-		TokenURL:         serverURL + "/token",
-		AuthorizationURL: serverURL + "/authorize",
-		UserInfoURL:      serverURL + "/userinfo",
-		Scopes:           []string{"openid", "groups"},
-		UserIDKey:        "user_id_key",
-	}
-
-	testConfig.ClaimMapping.UserNameKey = "user_name_key"
-	testConfig.ClaimMapping.GroupsKey = "groups_key"
-	testConfig.ClaimMapping.EmailKey = "mail"
-	testConfig.ClaimMapping.EmailVerifiedKey = "has_verified_email"
-
-	log := logrus.New()
-
-	conn, err := testConfig.Open("id", log)
-	if err != nil {
-		t.Fatal(err)
-	}
-
-	oauthConn, ok := conn.(*oauthConnector)
-	if !ok {
-		t.Fatal(errors.New("failed to convert to oauthConnector"))
-	}
-
-	return oauthConn
-}
-
-func newRequestWithAuthCode(t *testing.T, serverURL string, code string) *http.Request {
-	req, err := http.NewRequest("GET", serverURL, nil)
-	if err != nil {
-		t.Fatal("failed to create request", err)
-	}
-
-	values := req.URL.Query()
-	values.Add("code", code)
-	req.URL.RawQuery = values.Encode()
-
-	return req
-}
diff --git a/server/server_test.go b/server/server_test.go
index 6f4bcb81..12130dfa 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -481,6 +481,47 @@ func makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time)
 					return nil
 				},
 			},
+			{
+				name:   "refresh with different client id",
+				scopes: []string{"openid", "email"},
+				handleToken: func(ctx context.Context, p *oidc.Provider, config *oauth2.Config, token *oauth2.Token, conn *mock.Callback) error {
+					v := url.Values{}
+					v.Add("client_id", clientID)
+					v.Add("client_secret", clientSecret)
+					v.Add("grant_type", "refresh_token")
+					v.Add("refresh_token", "existedrefrestoken")
+					v.Add("scope", "oidc email")
+					resp, err := http.PostForm(p.Endpoint().TokenURL, v)
+					if err != nil {
+						return err
+					}
+
+					defer resp.Body.Close()
+					if resp.StatusCode != http.StatusBadRequest {
+						return fmt.Errorf("expected status code %d, got %d", http.StatusBadRequest, resp.StatusCode)
+					}
+
+					var respErr struct {
+						Error       string `json:"error"`
+						Description string `json:"error_description"`
+					}
+
+					if err = json.NewDecoder(resp.Body).Decode(&respErr); err != nil {
+						return fmt.Errorf("cannot decode token response: %v", err)
+					}
+
+					if respErr.Error != errInvalidGrant {
+						return fmt.Errorf("expected error %q, got %q", errInvalidGrant, respErr.Error)
+					}
+
+					expectedMsg := "Refresh token is invalid or has already been claimed by another client."
+					if respErr.Description != expectedMsg {
+						return fmt.Errorf("expected error description %q, got %q", expectedMsg, respErr.Description)
+					}
+
+					return nil
+				},
+			},
 			{
 				// This test ensures that the connector.RefreshConnector interface is being
 				// used when clients request a refresh token.
@@ -792,6 +833,13 @@ func TestOAuth2CodeFlow(t *testing.T) {
 				t.Fatalf("failed to create client: %v", err)
 			}
 
+			if err := s.storage.CreateRefresh(storage.RefreshToken{
+				ID:       "existedrefrestoken",
+				ClientID: "unexcistedclientid",
+			}); err != nil {
+				t.Fatalf("failed to create existed refresh token: %v", err)
+			}
+
 			// Create the OAuth2 config.
 			oauth2Config = &oauth2.Config{
 				ClientID:     client.ID,
@@ -1570,6 +1618,13 @@ func TestOAuth2DeviceFlow(t *testing.T) {
 					t.Fatalf("failed to create client: %v", err)
 				}
 
+				if err := s.storage.CreateRefresh(storage.RefreshToken{
+					ID:       "existedrefrestoken",
+					ClientID: "unexcistedclientid",
+				}); err != nil {
+					t.Fatalf("failed to create existed refresh token: %v", err)
+				}
+
 				// Grab the issuer that we'll reuse for the different endpoints to hit
 				issuer, err := url.Parse(s.issuerURL.String())
 				if err != nil {
diff --git a/storage/kubernetes/storage_test.go b/storage/kubernetes/storage_test.go
index 4571327a..5033ad06 100644
--- a/storage/kubernetes/storage_test.go
+++ b/storage/kubernetes/storage_test.go
@@ -262,7 +262,7 @@ func TestRetryOnConflict(t *testing.T) {
 		{
 			"Timeout reached",
 			func() error { err := httpErr{status: 409}; return error(&err) },
-			"maximum timeout reached while retrying a conflicted request:   Conflict: response from server \"\"",
+			"maximum timeout reached while retrying a conflicted request",
 		},
 		{
 			"HTTP Error",