Skip to content

Commit 90f08f0

Browse files
committed
Fix access cookie expiration, remove related legacy code
1 parent 1623589 commit 90f08f0

File tree

7 files changed

+226
-139
lines changed

7 files changed

+226
-139
lines changed

docs/content/configuration/_index.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,6 @@ weight: 2
8080
| \--opa-authz-uri | OPA endpoint address with path | | PROXY_OPA_AUTHZ_URI
8181
| \--pat-retry-count | number of retries to get PAT | 5 | PROXY_PAT_RETRY_COUNT
8282
| \--pat-retry-interval | interval between retries to get PAT | 2s | PROXY_PAT_RETRY_INTERVAL
83-
| \--access-token-duration value | fallback cookie duration for the access token when using refresh tokens | 720h0m0s | PROXY_ACCESS_TOKEN_DURATION
8483
| \--cookie-domain value | domain the access cookie is available to, defaults host header | | PROXY_COOKIE_DOMAIN
8584
| \--cookie-path value | path to which cookie is available | | PROXY_COOKIE_PATH
8685
| \--cookie-access-name value | name of the cookie use to hold the access token | kc-access | PROXY_COOKIE_ACCESS_NAME

e2e/e2e_test.go

Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -981,6 +981,175 @@ var _ = Describe("Code Flow login/logout", func() {
981981
})
982982
})
983983

984+
var _ = Describe("Code Flow login/logout", func() {
985+
var (
986+
portNum string
987+
proxyAddress string
988+
server *http.Server
989+
)
990+
991+
errGroup, _ := errgroup.WithContext(context.Background())
992+
993+
AfterEach(func() {
994+
if server != nil {
995+
err := server.Shutdown(context.Background())
996+
Expect(err).NotTo(HaveOccurred())
997+
}
998+
999+
if errGroup != nil {
1000+
err := errGroup.Wait()
1001+
Expect(err).NotTo(HaveOccurred())
1002+
}
1003+
})
1004+
1005+
BeforeEach(func() {
1006+
var (
1007+
err error
1008+
upstreamSvcPort string
1009+
)
1010+
1011+
server, upstreamSvcPort = startAndWaitTestUpstream(errGroup, false, false, false)
1012+
portNum, err = generateRandomPort()
1013+
Expect(err).NotTo(HaveOccurred())
1014+
1015+
proxyAddress = localURI + portNum
1016+
1017+
proxyArgs := []string{
1018+
"--discovery-url=" + idpRealmURI,
1019+
"--openid-provider-timeout=300s",
1020+
"--tls-openid-provider-ca-certificate=" + tlsCaCertificate,
1021+
"--tls-openid-provider-client-certificate=" + tlsCertificate,
1022+
"--tls-openid-provider-client-private-key=" + tlsPrivateKey,
1023+
"--listen=" + allInterfaces + portNum,
1024+
"--client-id=" + testClient,
1025+
"--client-secret=" + testClientSecret,
1026+
"--upstream-url=" + localURI + upstreamSvcPort,
1027+
"--no-redirects=false",
1028+
"--skip-access-token-clientid-check=true",
1029+
"--skip-access-token-issuer-check=true",
1030+
"--enable-idp-session-check=false",
1031+
"--enable-default-deny=false",
1032+
"--resources=uri=/*|roles=uma_authorization,offline_access",
1033+
"--openid-provider-retry-count=30",
1034+
"--enable-refresh-tokens=true",
1035+
"--encryption-key=" + testKey,
1036+
"--secure-cookie=false",
1037+
"--post-login-redirect-path=" + postLoginRedirectPath,
1038+
"--enable-register-handler=true",
1039+
"--enable-encrypted-token=false",
1040+
"--enable-id-token-claims=true",
1041+
"--enable-id-token-cookie=true",
1042+
"--enable-user-info-claims=true",
1043+
"--add-claims=email_verified",
1044+
"--add-claims=email",
1045+
"--enable-pkce=false",
1046+
"--tls-cert=" + tlsCertificate,
1047+
"--tls-private-key=" + tlsPrivateKey,
1048+
"--upstream-ca=" + tlsCaCertificate,
1049+
"--enable-session-cookies=false",
1050+
}
1051+
1052+
osArgs := make([]string, 0, 1+len(proxyArgs))
1053+
osArgs = append(osArgs, os.Args[0])
1054+
osArgs = append(osArgs, proxyArgs...)
1055+
startAndWait(portNum, osArgs)
1056+
})
1057+
1058+
When("Performing standard login with session cookies disabled", func() {
1059+
It("should login with user/password and logout successfully",
1060+
Label("code_flow"),
1061+
Label("session_cookies_disabled"),
1062+
func(_ context.Context) {
1063+
var err error
1064+
1065+
rClient := resty.New()
1066+
rClient.SetTLSClientConfig(&tls.Config{RootCAs: caPool, MinVersion: tls.VersionTLS13})
1067+
resp := codeFlowLogin(rClient, proxyAddress, http.StatusOK, testUser, testPass)
1068+
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))
1069+
body := resp.Body()
1070+
Expect(strings.Contains(string(body), postLoginRedirectPath)).To(BeTrue())
1071+
1072+
jarURI, err := url.Parse(proxyAddress)
1073+
Expect(err).NotTo(HaveOccurred())
1074+
1075+
cookiesLogin := rClient.GetClient().Jar.Cookies(jarURI)
1076+
1077+
var accessCookieLogin string
1078+
1079+
for _, cook := range cookiesLogin {
1080+
if cook.Name == constant.AccessCookie {
1081+
accessCookieLogin = cook.Value
1082+
}
1083+
}
1084+
1085+
time.Sleep(testAccessTokenExp)
1086+
1087+
resp, err = rClient.R().Get(proxyAddress + anyURI)
1088+
Expect(err).NotTo(HaveOccurred())
1089+
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))
1090+
body = resp.Body()
1091+
Expect(strings.Contains(string(body), anyURI)).To(BeTrue())
1092+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
1093+
Expect(err).NotTo(HaveOccurred())
1094+
1095+
cookiesAfterRefresh := resp.Cookies()
1096+
1097+
var (
1098+
accessCookieAfterRefresh *http.Cookie
1099+
refreshCookieAfterRefresh *http.Cookie
1100+
testCookie string
1101+
)
1102+
1103+
for _, cook := range cookiesAfterRefresh {
1104+
if cook.Name == constant.AccessCookie {
1105+
accessCookieAfterRefresh = cook
1106+
}
1107+
1108+
if cook.Name == constant.RefreshCookie {
1109+
refreshCookieAfterRefresh = cook
1110+
}
1111+
1112+
if cook.Name == testCookieValue {
1113+
testCookie = cook.Value
1114+
}
1115+
}
1116+
1117+
Expect(testCookie).To(Equal("test_value"))
1118+
1119+
_, err = jwt.ParseSigned(accessCookieAfterRefresh.Value, constant.SignatureAlgs[:])
1120+
Expect(err).NotTo(HaveOccurred())
1121+
Expect(accessCookieLogin).NotTo(Equal(accessCookieAfterRefresh.Value))
1122+
1123+
refresh, err := encryption.DecodeText(refreshCookieAfterRefresh.Value, testKey)
1124+
Expect(err).NotTo(HaveOccurred())
1125+
1126+
_, err = jwt.ParseSigned(refresh, constant.SignatureAlgs[:])
1127+
Expect(err).NotTo(HaveOccurred())
1128+
1129+
Expect(accessCookieAfterRefresh.Expires).To(Equal(refreshCookieAfterRefresh.Expires))
1130+
1131+
resp, err = rClient.R().Get(proxyAddress + anyURI)
1132+
Expect(err).NotTo(HaveOccurred())
1133+
Expect(resp.Header().Get("Proxy-Accepted")).To(Equal("true"))
1134+
body = resp.Body()
1135+
By(string(body))
1136+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
1137+
Expect(strings.Contains(string(body), anyURI)).To(BeTrue())
1138+
Expect(body).To(ContainSubstring(`"X-Auth-Email-Verified":["true"]`))
1139+
Expect(body).To(ContainSubstring(`"X-Auth-Email":["somebody@somewhere.com"]`))
1140+
1141+
resp, err = rClient.R().Get(proxyAddress + logoutURI)
1142+
Expect(err).NotTo(HaveOccurred())
1143+
Expect(resp.StatusCode()).To(Equal(http.StatusOK))
1144+
1145+
rClient.SetRedirectPolicy(resty.NoRedirectPolicy())
1146+
resp, _ = rClient.R().Get(proxyAddress)
1147+
Expect(resp.StatusCode()).To(Equal(http.StatusSeeOther))
1148+
},
1149+
)
1150+
})
1151+
})
1152+
9841153
var _ = Describe("Code Flow login/logout mTLS", func() {
9851154
var (
9861155
portNum string

pkg/keycloak/config/config.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,6 @@ type Config struct {
125125
Scopes []string `json:"scopes,omitempty" usage:"list of scopes requested when authenticating the user" yaml:"scopes"`
126126
OpaTimeout time.Duration `env:"OPA_TIMEOUT" json:"opa-timeout,omitempty" usage:"timeout for connection to OPA" yaml:"opa-timeout"`
127127
PatRetryInterval time.Duration `env:"PAT_RETRY_INTERVAL" json:"pat-retry-interval,omitempty" usage:"interval between retries to get PAT" yaml:"pat-retry-interval"`
128-
AccessTokenDuration time.Duration `env:"ACCESS_TOKEN_DURATION" json:"access-token-duration,omitempty" usage:"fallback cookie duration for the access token when using refresh tokens" yaml:"access-token-duration"`
129128
PatRetryCount int `env:"PAT_RETRY_COUNT" json:"pat-retry-count,omitempty" usage:"number of retries to get PAT" yaml:"pat-retry-count"`
130129
CorsMaxAge time.Duration `env:"CORS_MAX_AGE" json:"cors-max-age,omitempty" usage:"max age applied to cors headers (Access-Control-Max-Age)" yaml:"cors-max-age"`
131130
UpstreamTimeout time.Duration `env:"UPSTREAM_TIMEOUT" json:"upstream-timeout,omitempty" usage:"maximum amount of time a dial will wait for a connect to complete" yaml:"upstream-timeout"`
@@ -219,7 +218,6 @@ func NewDefaultConfig() *Config {
219218
hostnames = append(hostnames, []string{"localhost", "127.0.0.1", "::1"}...)
220219

221220
return &Config{
222-
AccessTokenDuration: time.Duration(constant.FallbackAccessTokenDuration) * time.Hour,
223221
CookieAccessName: constant.AccessCookie,
224222
CookieIDTokenName: constant.IDTokenCookie,
225223
CookieRefreshName: constant.RefreshCookie,

pkg/keycloak/proxy/handlers.go

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -477,7 +477,6 @@ func loginHandler(
477477
enableCompressToken bool,
478478
compressTokenOnlyAuthScheme string,
479479
cookManager *cookie.Manager,
480-
accessTokenDuration time.Duration,
481480
store storage.Storage,
482481
compressTokenPool *utils.LimitedBufferPool,
483482
) func(wrt http.ResponseWriter, req *http.Request) {
@@ -649,8 +648,15 @@ func loginHandler(
649648
}
650649
}
651650

652-
refreshExpiry := session.GetAccessCookieExpiration(scope.Logger, accessTokenDuration, token.RefreshToken)
653-
// drop in the access token - cookie expiration = access token
651+
stdRefreshClaims, err := utils.ParseRefreshToken(token.RefreshToken)
652+
if err != nil {
653+
scope.Logger.Error(apperrors.ErrEncryptRefreshToken.Error(), zap.Error(err))
654+
655+
return http.StatusInternalServerError,
656+
errors.Join(apperrors.ErrParseRefreshToken, err)
657+
}
658+
659+
refreshExpiry := time.Until(stdRefreshClaims.Expiry.Time())
654660
cookManager.DropAccessTokenCookie(
655661
req,
656662
writer,
@@ -667,38 +673,20 @@ func loginHandler(
667673
)
668674
}
669675

670-
var expiration time.Duration
671-
// notes: not all idp refresh tokens are readable, google for example, so we attempt to decode into
672-
// a jwt and if possible extract the expiration, else we default to 10 days
673-
refreshTokenObj, errRef := jwt.ParseSigned(token.RefreshToken, constant.SignatureAlgs[:])
674-
if errRef != nil {
675-
return http.StatusInternalServerError,
676-
errors.Join(apperrors.ErrParseRefreshToken, errRef)
677-
}
678-
679-
stdRefreshClaims := &jwt.Claims{}
680-
681-
err = refreshTokenObj.UnsafeClaimsWithoutVerification(stdRefreshClaims)
682-
if err != nil {
683-
expiration = 0
684-
} else {
685-
expiration = time.Until(stdRefreshClaims.Expiry.Time())
686-
}
687-
688676
switch store != nil {
689677
case true:
690678
rCtx, rCancel := context.WithTimeout(ctx, constant.RedisTimeout)
691679
defer rCancel()
692680

693-
err = store.Set(rCtx, identity.ID, refreshToken, expiration)
681+
err = store.Set(rCtx, identity.ID, refreshToken, refreshExpiry)
694682
if err != nil {
695683
scope.Logger.Error(
696684
apperrors.ErrSaveTokToStore.Error(),
697685
zap.Error(err),
698686
)
699687
}
700688
default:
701-
cookManager.DropRefreshTokenCookie(req, writer, refreshToken, expiration)
689+
cookManager.DropRefreshTokenCookie(req, writer, refreshToken, refreshExpiry)
702690
}
703691
} else {
704692
cookManager.DropAccessTokenCookie(

pkg/keycloak/proxy/server.go

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -562,7 +562,6 @@ func (r *OauthProxy) CreateReverseProxy() error {
562562
r.Config.EncryptionKey,
563563
newOAuth2Config,
564564
r.Store,
565-
r.Config.AccessTokenDuration,
566565
r.Config.EnableOptionalEncryption,
567566
r.Config.EnableCompressToken,
568567
r.Config.CompressTokenOnlyAuthScheme,
@@ -586,7 +585,6 @@ func (r *OauthProxy) CreateReverseProxy() error {
586585
r.Config.EnableCompressToken,
587586
r.Config.CompressTokenOnlyAuthScheme,
588587
r.Cm,
589-
r.Config.AccessTokenDuration,
590588
r.Store,
591589
compressTokenPool,
592590
)

0 commit comments

Comments
 (0)