Skip to content
Snippets Groups Projects
Commit 5fc685aa authored by Jan Garaj's avatar Jan Garaj Committed by Rohith Jayawardene
Browse files

Use divided cookies for large access/refresh token cookie - fix #278 (#279)

parent 8c1c2ed9
Branches
Tags
No related merge requests found
......@@ -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**
----
......
......@@ -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) {
// 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) {
// 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
}
}
}
......@@ -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)
}
}
if token.Len() == 0 {
return "", ErrSessionNotFound
}
return token.String(), nil
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment