diff --git a/README.md b/README.md index 2f72508690a43e4d3944d1cea1f4f1ae6a67f6dd..ad1624ba16c342789615b1e504e23cab304aba7a 100644 --- a/README.md +++ b/README.md @@ -535,6 +535,19 @@ You can control the upstream endpoint via the --upstream-url option. Both http a Assuming the *--enable-metrics* has been set, a Prometheus endpoint can be found on */oauth/metrics*; at present the only metric being exposed is a counter per http code. +### Limitations + +Keep in mind [browser cookie limits](http://browsercookielimits.squawky.net/), if you use access or +refresh tokens in the browser cookie. Keycloak-proxy divides cookie automatically if your cookie +is longer than 4093 bytes. Real size of the cookie depends on the content of the issued access token. +Also, encryption might add additional bytes to the cookie size. If you have large cookies (>200 KB), +you might reach browser cookie limits. + +All cookies are part of the header request, so you might find a problem with the max headers size +limits in your infrastructure (some load balancers have very low this value, such as 8 KB). Be +sure that all network devices have sufficient header size limits. Otherwise, your users won't be +able to obtain access token. + ### **Contribution Guidelines** ---- diff --git a/cookies.go b/cookies.go index a852d1b39d62f94cff4a734d704a4ef3a811f2c3..a257a126f7c70dcb6055c6a5c815f4126684c8a9 100644 --- a/cookies.go +++ b/cookies.go @@ -17,6 +17,7 @@ package main import ( "net/http" + "strconv" "strings" "time" ) @@ -45,12 +46,42 @@ func (r *oauthProxy) dropCookie(w http.ResponseWriter, host, name, value string, // dropAccessTokenCookie drops a access token cookie into the response func (r *oauthProxy) dropAccessTokenCookie(req *http.Request, w http.ResponseWriter, value string, duration time.Duration) { - r.dropCookie(w, req.Host, r.config.CookieAccessName, value, duration) + // also cookie name is included in the cookie length; cookie name suffix "-xxx" + maxCookieLenght := 4089 - len(r.config.CookieAccessName) + + if len(value) <= maxCookieLenght { + r.dropCookie(w, req.Host, r.config.CookieAccessName, value, duration) + } else { + // write divided cookies because payload is too long for single cookie + r.dropCookie(w, req.Host, r.config.CookieAccessName, value[0:maxCookieLenght], duration) + for i := maxCookieLenght; i < len(value); i += maxCookieLenght { + end := i + maxCookieLenght + if end > len(value) { + end = len(value) + } + r.dropCookie(w, req.Host, r.config.CookieAccessName+"-"+strconv.Itoa(i/maxCookieLenght), value[i:end], duration) + } + } } // dropRefreshTokenCookie drops a refresh token cookie into the response func (r *oauthProxy) dropRefreshTokenCookie(req *http.Request, w http.ResponseWriter, value string, duration time.Duration) { - r.dropCookie(w, req.Host, r.config.CookieRefreshName, value, duration) + // also cookie name is included in the cookie length; cookie name suffix "-xxx" + maxCookieLenght := 4089 - len(r.config.CookieRefreshName) + + if len(value) <= maxCookieLenght { + r.dropCookie(w, req.Host, r.config.CookieRefreshName, value, duration) + } else { + // write divided cookies because payload is too long for single cookie + r.dropCookie(w, req.Host, r.config.CookieRefreshName, value[0:maxCookieLenght], duration) + for i := maxCookieLenght; i < len(value); i += maxCookieLenght { + end := i + maxCookieLenght + if end > len(value) { + end = len(value) + } + r.dropCookie(w, req.Host, r.config.CookieRefreshName+"-"+strconv.Itoa(i/maxCookieLenght), value[i:end], duration) + } + } } // clearAllCookies is just a helper function for the below @@ -62,9 +93,29 @@ func (r *oauthProxy) clearAllCookies(req *http.Request, w http.ResponseWriter) { // clearRefreshSessionCookie clears the session cookie func (r *oauthProxy) clearRefreshTokenCookie(req *http.Request, w http.ResponseWriter) { r.dropCookie(w, req.Host, r.config.CookieRefreshName, "", -10*time.Hour) + + // clear divided cookies + for i := 1; i < 600; i++ { + var _, err = req.Cookie(r.config.CookieRefreshName + "-" + strconv.Itoa(i)) + if err == nil { + r.dropCookie(w, req.Host, r.config.CookieRefreshName+"-"+strconv.Itoa(i), "", -10*time.Hour) + } else { + break + } + } } // clearAccessTokenCookie clears the session cookie func (r *oauthProxy) clearAccessTokenCookie(req *http.Request, w http.ResponseWriter) { r.dropCookie(w, req.Host, r.config.CookieAccessName, "", -10*time.Hour) + + // clear divided cookies + for i := 1; i < len(req.Cookies()); i++ { + var _, err = req.Cookie(r.config.CookieAccessName + "-" + strconv.Itoa(i)) + if err == nil { + r.dropCookie(w, req.Host, r.config.CookieAccessName+"-"+strconv.Itoa(i), "", -10*time.Hour) + } else { + break + } + } } diff --git a/session.go b/session.go index 6bb11f90ac49b2cd1a228824211dec7118416e1d..bd47ff9ef85798e36f803ef3a5eac3c85d6ceef8 100644 --- a/session.go +++ b/session.go @@ -16,7 +16,9 @@ limitations under the License. package main import ( + "bytes" "net/http" + "strconv" "strings" "github.com/gambol99/go-oidc/jose" @@ -100,8 +102,25 @@ func getTokenInBearer(req *http.Request) (string, error) { // getTokenInCookie retrieves the access token from the request cookies func getTokenInCookie(req *http.Request, name string) (string, error) { + var token bytes.Buffer + if cookie := findCookie(name, req.Cookies()); cookie != nil { - return cookie.Value, nil + token.WriteString(cookie.Value) + } + + // add also divided cookies + for i := 1; i < 600; i++ { + cookie := findCookie(name+"-"+strconv.Itoa(i), req.Cookies()) + if cookie == nil { + break + } else { + token.WriteString(cookie.Value) + } } - return "", ErrSessionNotFound + + if token.Len() == 0 { + return "", ErrSessionNotFound + } + + return token.String(), nil }