diff --git a/.gitignore b/.gitignore
index 57f3044462..393f27d53d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -41,3 +41,5 @@ _testmain.go
# vi Dockerfile.dev
# docker build -f Dockerfile.dev .
Dockerfile.dev
+
+obj
\ No newline at end of file
diff --git a/Makefile b/Makefile
index fdfda8eafd..e373766e95 100644
--- a/Makefile
+++ b/Makefile
@@ -116,3 +116,7 @@ validate-go-version:
.PHONY: local-env-%
local-env-%:
make -C contrib/local-environment $*
+
+.PHONY: local-debug-build
+local-debug-build:
+ go build -gcflags="all=-N -l" -o app.exe
\ No newline at end of file
diff --git a/docs/docs/configuration/alpha_config.md b/docs/docs/configuration/alpha_config.md
index 24e6a429a1..03f10faa17 100644
--- a/docs/docs/configuration/alpha_config.md
+++ b/docs/docs/configuration/alpha_config.md
@@ -388,6 +388,8 @@ character.
| `userIDClaim` | _string_ | UserIDClaim indicates which claim contains the user ID
default set to 'email' |
| `audienceClaims` | _[]string_ | AudienceClaim allows to define any claim that is verified against the client id
By default `aud` claim is used for verification. |
| `extraAudiences` | _[]string_ | ExtraAudiences is a list of additional audiences that are allowed
to pass verification in addition to the client id. |
+| `enableCookieRefresh` | _bool_ | Enable cookie refresh functionality that is going to be triggered every time the session is updated |
+| `cookieRefreshName` | _string_ | Name of the cookie that is going to be extracted from the request and refreshed |
### Provider
diff --git a/main_test.go b/main_test.go
index 6a640817b7..5bce633546 100644
--- a/main_test.go
+++ b/main_test.go
@@ -77,6 +77,7 @@ providers:
insecureSkipNonce: true
audienceClaims: [aud]
extraAudiences: []
+ cookieRefreshName: 'hsdpamcookie'
loginURLParameters:
- name: approval_prompt
default:
@@ -157,6 +158,7 @@ redirect_url="http://localhost:4180/oauth2/callback"
AudienceClaims: []string{"aud"},
ExtraAudiences: []string{},
InsecureSkipNonce: true,
+ CookieRefreshName: "hsdpamcookie",
},
LoginURLParameters: []options.LoginURLParameter{
{Name: "approval_prompt", Default: []string{"force"}},
diff --git a/oauthproxy.go b/oauthproxy.go
index d11040c380..4eb58e0ccc 100644
--- a/oauthproxy.go
+++ b/oauthproxy.go
@@ -393,6 +393,12 @@ func buildSessionChain(opts *options.Options, provider providers.Provider, sessi
ValidateSession: provider.ValidateSession,
}))
+ oidcProviderSettings := opts.Providers[0].OIDCConfig
+ if oidcProviderSettings.EnableCookieRefresh {
+ chain = chain.Append(middleware.NewCookieRefresh(&middleware.CookieRefreshOptions{IssuerURL: oidcProviderSettings.IssuerURL, CookieRefreshName: oidcProviderSettings.CookieRefreshName}))
+ logger.Printf("Enabling OIDC cookie refresh for the cookie '%s' functionality because OIDCEnableCookieRefresh is enabled", oidcProviderSettings.CookieRefreshName)
+ }
+
return chain
}
diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go
index 616b33abcb..c3c010a289 100644
--- a/pkg/apis/options/legacy_options.go
+++ b/pkg/apis/options/legacy_options.go
@@ -543,6 +543,8 @@ type LegacyProvider struct {
OIDCGroupsClaim string `flag:"oidc-groups-claim" cfg:"oidc_groups_claim"`
OIDCAudienceClaims []string `flag:"oidc-audience-claim" cfg:"oidc_audience_claims"`
OIDCExtraAudiences []string `flag:"oidc-extra-audience" cfg:"oidc_extra_audiences"`
+ OIDCEnableCookieRefresh bool `flag:"oidc-enable-cookie-refresh" cfg:"oidc_enable_cookie_refresh"`
+ OIDCCookieRefreshName string `flag:"oidc-cookie-refresh-name" cfg:"oidc_cookie_refresh_name"`
LoginURL string `flag:"login-url" cfg:"login_url"`
RedeemURL string `flag:"redeem-url" cfg:"redeem_url"`
ProfileURL string `flag:"profile-url" cfg:"profile_url"`
@@ -601,6 +603,8 @@ func legacyProviderFlagSet() *pflag.FlagSet {
flagSet.String("oidc-email-claim", OIDCEmailClaim, "which OIDC claim contains the user's email")
flagSet.StringSlice("oidc-audience-claim", OIDCAudienceClaims, "which OIDC claims are used as audience to verify against client id")
flagSet.StringSlice("oidc-extra-audience", []string{}, "additional audiences allowed to pass audience verification")
+ flagSet.Bool("oidc-enable-cookie-refresh", false, "Refresh the OIDC provider cookies to enable SSO in an extended period of time")
+ flagSet.String("oidc-cookie-refresh-name", "hsdpamcookie", "The name of the cookie that the OIDC provider uses to keep its session fresh")
flagSet.String("login-url", "", "Authentication endpoint")
flagSet.String("redeem-url", "", "Token redemption endpoint")
flagSet.String("profile-url", "", "Profile access endpoint")
@@ -702,6 +706,8 @@ func (l *LegacyProvider) convert() (Providers, error) {
GroupsClaim: l.OIDCGroupsClaim,
AudienceClaims: l.OIDCAudienceClaims,
ExtraAudiences: l.OIDCExtraAudiences,
+ EnableCookieRefresh: l.OIDCEnableCookieRefresh,
+ CookieRefreshName: l.OIDCCookieRefreshName,
}
// Support for legacy configuration option
diff --git a/pkg/apis/options/load_test.go b/pkg/apis/options/load_test.go
index cface5140b..e5d9097816 100644
--- a/pkg/apis/options/load_test.go
+++ b/pkg/apis/options/load_test.go
@@ -44,6 +44,7 @@ var _ = Describe("Load", func() {
OIDCGroupsClaim: "groups",
OIDCAudienceClaims: []string{"aud"},
InsecureOIDCSkipNonce: true,
+ OIDCCookieRefreshName: "hsdpamcookie",
},
Options: Options{
diff --git a/pkg/apis/options/providers.go b/pkg/apis/options/providers.go
index 7b9934051c..a7e02b7d17 100644
--- a/pkg/apis/options/providers.go
+++ b/pkg/apis/options/providers.go
@@ -230,6 +230,10 @@ type OIDCOptions struct {
// ExtraAudiences is a list of additional audiences that are allowed
// to pass verification in addition to the client id.
ExtraAudiences []string `json:"extraAudiences,omitempty"`
+ // Enable cookie refresh functionality that is going to be triggered every time the session is updated
+ EnableCookieRefresh bool `json:"enableCookieRefresh,omitempty"`
+ // Name of the cookie that is going to be extracted from the request and refreshed
+ CookieRefreshName string `json:"cookieRefreshName,omitempty"`
}
type LoginGovOptions struct {
diff --git a/pkg/apis/sessions/session_state.go b/pkg/apis/sessions/session_state.go
index 2fd5161347..43075adcd7 100644
--- a/pkg/apis/sessions/session_state.go
+++ b/pkg/apis/sessions/session_state.go
@@ -31,8 +31,9 @@ type SessionState struct {
IntrospectClaims string `msgpack:"ic,omitempty"`
// Internal helpers, not serialized
- Clock clock.Clock `msgpack:"-"`
- Lock Lock `msgpack:"-"`
+ Clock clock.Clock `msgpack:"-"`
+ Lock Lock `msgpack:"-"`
+ SessionJustRefreshed bool `msgpack:"-"`
}
func (s *SessionState) ObtainLock(ctx context.Context, expiration time.Duration) error {
diff --git a/pkg/middleware/cookie_refresh.go b/pkg/middleware/cookie_refresh.go
new file mode 100644
index 0000000000..a64c161f15
--- /dev/null
+++ b/pkg/middleware/cookie_refresh.go
@@ -0,0 +1,62 @@
+package middleware
+
+import (
+ "fmt"
+ "net/http"
+
+ "github.com/justinas/alice"
+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/apis/middleware"
+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
+ "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests"
+)
+
+type CookieRefreshOptions struct {
+ IssuerURL string
+ CookieRefreshName string
+}
+
+func NewCookieRefresh(opts *CookieRefreshOptions) alice.Constructor {
+ cr := &cookieRefresh{
+ HTTPClient: &http.Client{},
+ IssuerURL: opts.IssuerURL,
+ CookieRefreshName: opts.CookieRefreshName,
+ }
+ return cr.refreshCookie
+}
+
+type cookieRefresh struct {
+ HTTPClient *http.Client
+ IssuerURL string
+ CookieRefreshName string
+}
+
+func (cr *cookieRefresh) refreshCookie(next http.Handler) http.Handler {
+ return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ scope := middlewareapi.GetRequestScope(req)
+ if scope.Session == nil || !scope.Session.SessionJustRefreshed {
+ next.ServeHTTP(rw, req)
+ return
+ }
+
+ cookie, err := req.Cookie(cr.CookieRefreshName)
+ if err != nil {
+ logger.Errorf("SSO Cookie Refresher - Could find '%s' cookie in the request: %v", cr.CookieRefreshName, err)
+ return
+ }
+ resp := requests.New(fmt.Sprintf("%s/session/refresh", cr.IssuerURL)).
+ WithContext(req.Context()).
+ WithMethod("GET").
+ SetHeader("api-version", "1").
+ SetHeader("Cookie", fmt.Sprintf("%s=%s", cr.CookieRefreshName, cookie.Value)).
+ Do()
+
+ if resp.StatusCode() != http.StatusNoContent {
+ bodyString := string(resp.Body())
+ logger.Errorf("SSO Cookie Refresher - Could not refresh the '%s' cookie due to status and content: %v - %v", cr.CookieRefreshName, resp.StatusCode(), bodyString)
+ return
+ }
+
+ logger.Printf("SSO Cookie Refresher - Cookie '%s' refreshed", cr.CookieRefreshName)
+ next.ServeHTTP(rw, req)
+ })
+}
diff --git a/providers/oidc.go b/providers/oidc.go
index de7b827754..f55ec0e4fc 100644
--- a/providers/oidc.go
+++ b/providers/oidc.go
@@ -159,6 +159,7 @@ func (p *OIDCProvider) RefreshSession(ctx context.Context, s *sessions.SessionSt
if err != nil {
return false, fmt.Errorf("unable to redeem refresh token: %v", err)
}
+ s.SessionJustRefreshed = true
return true, nil
}