Repository: dexidp/dex
Pull request #1948 (2021-01-18 07:27:19)

Add Cache-control headers to token responses
Signed-off-by: m.nabokikh <maksim.nabokikh@flant.com>

<!--
Thank you for sending a pull request! Here 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
* Add headers
* Add tests

#### What this PR does / why we need it
https://tools.ietf.org/html/rfc6749#section-5.1
> The authorization server MUST include the HTTP "Cache-Control"
   response header field [RFC2616] with a value of "no-store" in any
   response containing tokens, credentials, or other sensitive
   information, as well as the "Pragma" response header field [RFC2616]
   with a value of "no-cache".

It is also ok (and better) to add Cache-Control header to device authorization response as in the example in https://tools.ietf.org/html/rfc8628#section-3.2

#### Special notes for your reviewer
Cache headers are required to pass the OpenID certification.

#### 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
Add "Cache-control: no-store" and "Pragma: no-cache" headers to token responses
```



Patch:
diff --git a/server/deviceflowhandlers.go b/server/deviceflowhandlers.go
index e0932c54..20b0a19b 100644
--- a/server/deviceflowhandlers.go
+++ b/server/deviceflowhandlers.go
@@ -140,6 +140,10 @@ func (s *Server) handleDeviceCode(w http.ResponseWriter, r *http.Request) {
 			PollInterval:            pollIntervalSeconds,
 		}
 
+		// Device Authorization Response can contain cache control header according to
+		// https://tools.ietf.org/html/rfc8628#section-3.2
+		w.Header().Set("Cache-Control", "no-store")
+
 		enc := json.NewEncoder(w)
 		enc.SetEscapeHTML(false)
 		enc.SetIndent("", "   ")
diff --git a/server/handlers.go b/server/handlers.go
index ec056a76..be9cf4e8 100644
--- a/server/handlers.go
+++ b/server/handlers.go
@@ -1476,6 +1476,10 @@ func (s *Server) writeAccessToken(w http.ResponseWriter, resp *accessTokenRespon
 	}
 	w.Header().Set("Content-Type", "application/json")
 	w.Header().Set("Content-Length", strconv.Itoa(len(data)))
+
+	// Token response must include cache headers https://tools.ietf.org/html/rfc6749#section-5.1
+	w.Header().Set("Cache-Control", "no-store")
+	w.Header().Set("Pragma", "no-cache")
 	w.Write(data)
 }
 


Test patch:
diff --git a/server/server_test.go b/server/server_test.go
index fa73743a..31753ff6 100644
--- a/server/server_test.go
+++ b/server/server_test.go
@@ -395,6 +395,12 @@ func makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time)
 						}
 						return fmt.Errorf("unexpected response: %s", dump)
 					}
+					if resp.Header.Get("Cache-Control") != "no-store" {
+						return fmt.Errorf("cache-control header doesn't included in token response")
+					}
+					if resp.Header.Get("Pragma") != "no-cache" {
+						return fmt.Errorf("pragma header doesn't included in token response")
+					}
 					return nil
 				},
 			},
@@ -423,6 +429,12 @@ func makeOAuth2Tests(clientID string, clientSecret string, now func() time.Time)
 						}
 						return fmt.Errorf("unexpected response: %s", dump)
 					}
+					if resp.Header.Get("Cache-Control") != "no-store" {
+						return fmt.Errorf("cache-control header doesn't included in token response")
+					}
+					if resp.Header.Get("Pragma") != "no-cache" {
+						return fmt.Errorf("pragma header doesn't included in token response")
+					}
 					return nil
 				},
 			},
@@ -701,6 +713,7 @@ func TestOAuth2CodeFlow(t *testing.T) {
 						checkErrorResponse(err, t, tc)
 						return
 					}
+
 					if err != nil {
 						t.Errorf("failed to exchange code for token: %v", err)
 						return
@@ -1515,6 +1528,9 @@ func TestOAuth2DeviceFlow(t *testing.T) {
 			if resp.StatusCode != http.StatusOK {
 				t.Errorf("%v - Unexpected Response Type.  Expected 200 got  %v.  Response: %v", tc.name, resp.StatusCode, string(responseBody))
 			}
+			if resp.Header.Get("Cache-Control") != "no-store" {
+				t.Errorf("Cache-Control header doesn't exist in Device Code Response")
+			}
 
 			// Parse the code response
 			var deviceCode deviceCodeResponse