Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: return invalid_grant error on claiming token of another client #2344

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion server/refreshhandlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
55 changes: 55 additions & 0 deletions server/server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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 {
Expand Down