clientcredentials: add option for additional endpoint parameters

This is to support https://auth0.com/docs/api-auth/config/asking-for-access-tokens.

Fixes https://github.com/golang/oauth2/issues/216

Change-Id: I9b8fdb4fe22c688fd71e43bd21d80b796434b8b0
Reviewed-on: https://go-review.googlesource.com/36880
Run-TryBot: Ian Lance Taylor <iant@golang.org>
Reviewed-by: Jaana Burcu Dogan <jbd@google.com>
Reviewed-by: Brad Fitzpatrick <bradfitz@golang.org>
diff --git a/clientcredentials/clientcredentials.go b/clientcredentials/clientcredentials.go
index 26bc838..29374af 100644
--- a/clientcredentials/clientcredentials.go
+++ b/clientcredentials/clientcredentials.go
@@ -14,6 +14,7 @@
 package clientcredentials // import "golang.org/x/oauth2/clientcredentials"
 
 import (
+	"fmt"
 	"net/http"
 	"net/url"
 	"strings"
@@ -38,6 +39,9 @@
 
 	// Scope specifies optional requested permissions.
 	Scopes []string
+
+	// EndpointParams specifies additional parameters for requests to the token endpoint.
+	EndpointParams url.Values
 }
 
 // Token uses client credentials to retrieve a token.
@@ -76,10 +80,17 @@
 // Token refreshes the token by using a new client credentials request.
 // tokens received this way do not include a refresh token
 func (c *tokenSource) Token() (*oauth2.Token, error) {
-	tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, url.Values{
+	v := url.Values{
 		"grant_type": {"client_credentials"},
 		"scope":      internal.CondVal(strings.Join(c.conf.Scopes, " ")),
-	})
+	}
+	for k, p := range c.conf.EndpointParams {
+		if _, ok := v[k]; ok {
+			return nil, fmt.Errorf("oauth2: cannot overwrite parameter %q", k)
+		}
+		v[k] = p
+	}
+	tk, err := internal.RetrieveToken(c.ctx, c.conf.ClientID, c.conf.ClientSecret, c.conf.TokenURL, v)
 	if err != nil {
 		return nil, err
 	}
diff --git a/clientcredentials/clientcredentials_test.go b/clientcredentials/clientcredentials_test.go
index a18e95a..108520c 100644
--- a/clientcredentials/clientcredentials_test.go
+++ b/clientcredentials/clientcredentials_test.go
@@ -9,15 +9,17 @@
 	"io/ioutil"
 	"net/http"
 	"net/http/httptest"
+	"net/url"
 	"testing"
 )
 
-func newConf(url string) *Config {
+func newConf(serverURL string) *Config {
 	return &Config{
-		ClientID:     "CLIENT_ID",
-		ClientSecret: "CLIENT_SECRET",
-		Scopes:       []string{"scope1", "scope2"},
-		TokenURL:     url + "/token",
+		ClientID:       "CLIENT_ID",
+		ClientSecret:   "CLIENT_SECRET",
+		Scopes:         []string{"scope1", "scope2"},
+		TokenURL:       serverURL + "/token",
+		EndpointParams: url.Values{"audience": {"audience1"}},
 	}
 }
 
@@ -48,7 +50,7 @@
 		if err != nil {
 			t.Errorf("failed reading request body: %s.", err)
 		}
-		if string(body) != "grant_type=client_credentials&scope=scope1+scope2" {
+		if string(body) != "audience=audience1&grant_type=client_credentials&scope=scope1+scope2" {
 			t.Errorf("payload = %q; want %q", string(body), "grant_type=client_credentials&scope=scope1+scope2")
 		}
 		w.Header().Set("Content-Type", "application/x-www-form-urlencoded")
@@ -84,7 +86,7 @@
 			t.Errorf("Unexpected Content-Type header, %v is found.", headerContentType)
 		}
 		body, _ := ioutil.ReadAll(r.Body)
-		if string(body) != "grant_type=client_credentials&scope=scope1+scope2" {
+		if string(body) != "audience=audience1&grant_type=client_credentials&scope=scope1+scope2" {
 			t.Errorf("Unexpected refresh token payload, %v is found.", string(body))
 		}
 	}))