diff --git a/docs/README.md b/docs/README.md
index ff6204b5c..7c9191999 100644
--- a/docs/README.md
+++ b/docs/README.md
@@ -666,7 +666,9 @@ The CORS block configures the CORS (Cross-Origin Resource Sharing) behavior in C
| `token_endpoint` |
- ⚠ Mandatory.
- URL of the token endpoint at the authorization server.
|
| `client_id` | - ⚠ Mandatory.
- The client identifier.
|
| `client_secret` | - ⚠ Mandatory.
- The client password.
|
-| `retries` | - Optional.
- The number of retries to get the token und resource, if the resource-request responses with `401 Forbidden` HTTP status code.
- Default: `1`.
|
+| `retries` | - Optional.
- The number of retries to get the token and resource, if the resource-request responds with `401 Unauthorized` HTTP status code.
- Default: `1`.
|
+| `token_endpoint_auth_method` | - Optional.
- Defines the method to authenticate the client at the token endpoint.
- If set to `client_secret_post`, the client credentials are transported in the request body.
- If set to `client_secret_basic`, the client credentials are transported via Basic Authentication.
- Default: `client_secret_basic`.
|
+| `scope` | - Optional.
- A space separated list of requested scopes for the access token.
|
### Modifier
diff --git a/server/http_oauth2_test.go b/server/http_oauth2_test.go
index d54ae9b0f..cf73f178b 100644
--- a/server/http_oauth2_test.go
+++ b/server/http_oauth2_test.go
@@ -21,29 +21,6 @@ func TestEndpoints_OAuth2(t *testing.T) {
oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
if req.URL.Path == "/oauth2" {
- reqBody, _ := ioutil.ReadAll(req.Body)
- authorization := req.Header.Get("Authorization")
-
- if i == 0 {
- exp := `client_id=user&client_secret=pass+word&grant_type=client_credentials&scope=scope1+scope2`
- if exp != string(reqBody) {
- t.Errorf("want\n%s\ngot\n%s", exp, reqBody)
- }
- exp = ""
- if exp != authorization {
- t.Errorf("want\n%s\ngot\n%s", exp, authorization)
- }
- } else {
- exp := `grant_type=client_credentials`
- if exp != string(reqBody) {
- t.Errorf("want\n%s\ngot\n%s", exp, reqBody)
- }
- exp = "Basic dXNlcjpwYXNz"
- if exp != authorization {
- t.Errorf("want\n%s\ngot\n%s", exp, authorization)
- }
- }
-
rw.Header().Set("Content-Type", "application/json")
rw.WriteHeader(http.StatusOK)
@@ -131,3 +108,88 @@ func TestEndpoints_OAuth2(t *testing.T) {
shutdown()
}
}
+
+func TestEndpoints_OAuth2_Options(t *testing.T) {
+ helper := test.New(t)
+
+ type testCase struct {
+ configFile string
+ expBody string
+ expAuth string
+ }
+
+ for _, tc := range []testCase{
+ {
+ "01_couper.hcl",
+ `client_id=user&client_secret=pass+word&grant_type=client_credentials&scope=scope1+scope2`,
+ "",
+ },
+ {
+ "02_couper.hcl",
+ `grant_type=client_credentials`,
+ "Basic dXNlcjpwYXNz",
+ },
+ {
+ "03_couper.hcl",
+ `grant_type=client_credentials`,
+ "Basic dXNlcjpwYXNz",
+ },
+ } {
+ var tokenSeenCh chan struct{}
+
+ oauthOrigin := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ if req.URL.Path == "/options" {
+ reqBody, _ := ioutil.ReadAll(req.Body)
+ authorization := req.Header.Get("Authorization")
+
+ if tc.expBody != string(reqBody) {
+ t.Errorf("want\n%s\ngot\n%s", tc.expBody, reqBody)
+ }
+ if tc.expAuth != authorization {
+ t.Errorf("want\n%s\ngot\n%s", tc.expAuth, authorization)
+ }
+
+ rw.WriteHeader(http.StatusNoContent)
+
+ close(tokenSeenCh)
+ return
+ }
+ rw.WriteHeader(http.StatusBadRequest)
+ }))
+ defer oauthOrigin.Close()
+
+ confPath := fmt.Sprintf("testdata/oauth2/%s", tc.configFile)
+ shutdown, hook := newCouper(confPath, test.New(t))
+ defer func() {
+ if t.Failed() {
+ for _, e := range hook.Entries {
+ println(e.String())
+ }
+ }
+ shutdown()
+ }()
+
+ req, err := http.NewRequest(http.MethodGet, "http://anyserver:8080/", nil)
+ helper.Must(err)
+
+ req.Header.Set("X-Token-Endpoint", oauthOrigin.URL)
+
+ hook.Reset()
+
+ tokenSeenCh = make(chan struct{})
+
+ req.URL.Path = "/"
+ _, err = newClient().Do(req)
+ helper.Must(err)
+
+ timer := time.NewTimer(time.Second * 2)
+ select {
+ case <-timer.C:
+ t.Error("OAuth2 request failed")
+ case <-tokenSeenCh:
+ }
+
+ oauthOrigin.Close()
+ shutdown()
+ }
+}
diff --git a/server/testdata/oauth2/01_couper.hcl b/server/testdata/oauth2/01_couper.hcl
new file mode 100644
index 000000000..7bc2d4e69
--- /dev/null
+++ b/server/testdata/oauth2/01_couper.hcl
@@ -0,0 +1,25 @@
+server "oauth2-options" {
+ error_file = "./../integration/server_error.html"
+
+ api {
+ error_file = "./../integration/api_error.json"
+
+ endpoint "/" {
+ proxy {
+ backend {
+ url = "https://example.com/"
+
+ oauth2 {
+ token_endpoint = "${request.headers.x-token-endpoint}/options"
+ retries = 0
+ client_id = "user"
+ client_secret = "pass word"
+ grant_type = "client_credentials"
+ scope = "scope1 scope2"
+ token_endpoint_auth_method = "client_secret_post"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/server/testdata/oauth2/02_couper.hcl b/server/testdata/oauth2/02_couper.hcl
new file mode 100644
index 000000000..1f4cbcc24
--- /dev/null
+++ b/server/testdata/oauth2/02_couper.hcl
@@ -0,0 +1,24 @@
+server "oauth2-options" {
+ error_file = "./../integration/server_error.html"
+
+ api {
+ error_file = "./../integration/api_error.json"
+
+ endpoint "/" {
+ proxy {
+ backend {
+ url = "https://example.com/"
+
+ oauth2 {
+ token_endpoint = "${request.headers.x-token-endpoint}/options"
+ retries = 0
+ client_id = "user"
+ client_secret = "pass"
+ grant_type = "client_credentials"
+ token_endpoint_auth_method = "client_secret_basic"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/server/testdata/oauth2/03_couper.hcl b/server/testdata/oauth2/03_couper.hcl
new file mode 100644
index 000000000..6d31920d8
--- /dev/null
+++ b/server/testdata/oauth2/03_couper.hcl
@@ -0,0 +1,23 @@
+server "oauth2-options" {
+ error_file = "./../integration/server_error.html"
+
+ api {
+ error_file = "./../integration/api_error.json"
+
+ endpoint "/" {
+ proxy {
+ backend {
+ url = "https://example.com/"
+
+ oauth2 {
+ token_endpoint = "${request.headers.x-token-endpoint}/options"
+ retries = 0
+ client_id = "user"
+ client_secret = "pass"
+ grant_type = "client_credentials"
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/server/testdata/oauth2/0_retries_couper.hcl b/server/testdata/oauth2/0_retries_couper.hcl
index 9e8ec49e8..ee0df23a3 100644
--- a/server/testdata/oauth2/0_retries_couper.hcl
+++ b/server/testdata/oauth2/0_retries_couper.hcl
@@ -16,8 +16,6 @@ server "api" {
client_secret = "pass word"
grant_type = "client_credentials"
retries = 0
- scope = "scope1 scope2"
- token_endpoint_auth_method = "client_secret_post"
}
}
}
@@ -34,8 +32,6 @@ server "api" {
client_secret = "pass word"
grant_type = "client_credentials"
retries = 0
- scope = "scope1 scope2"
- token_endpoint_auth_method = "client_secret_post"
backend {
origin = "${request.headers.x-token-endpoint}"
path = "/oauth2"
diff --git a/server/testdata/oauth2/1_retries_couper.hcl b/server/testdata/oauth2/1_retries_couper.hcl
index 630b96a06..1a229b100 100644
--- a/server/testdata/oauth2/1_retries_couper.hcl
+++ b/server/testdata/oauth2/1_retries_couper.hcl
@@ -15,7 +15,6 @@ server "api" {
client_id = "user"
client_secret = "pass"
grant_type = "client_credentials"
- token_endpoint_auth_method = "client_secret_basic"
}
}
}
@@ -31,7 +30,6 @@ server "api" {
client_id = "user"
client_secret = "pass"
grant_type = "client_credentials"
- token_endpoint_auth_method = "client_secret_basic"
backend {
origin = "${request.headers.x-token-endpoint}"
path = "/oauth2"