diff --git a/CHANGELOG.md b/CHANGELOG.md
index f2c9b441..ea5b61ff 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -8,6 +8,8 @@
 
 ## Changes since v7.12.0
 
+- [#3217](https://github.com/oauth2-proxy/oauth2-proxy/pull/3217) feat: add --pass-refresh-token (@carillonator)
+
 # V7.12.0
 
 ## Release Highlights
@@ -119,7 +121,7 @@ For detailed information, migration guidance, and security implications, see the
 - 🕵️♀️ Vulnerabilities have been addressed
   - [CVE-2025-22871](https://github.com/advisories/GHSA-g9pc-8g42-g6vq)
 - 🐛 Squashed some bugs
-  
+
 ## Important Notes
 
 ## Breaking Changes
diff --git a/contrib/oauth2-proxy.cfg.example b/contrib/oauth2-proxy.cfg.example
index df16e4cc..5970d9df 100644
--- a/contrib/oauth2-proxy.cfg.example
+++ b/contrib/oauth2-proxy.cfg.example
@@ -59,6 +59,9 @@
 ## Pass OAuth Access token to upstream via "X-Forwarded-Access-Token"
 # pass_access_token = false
 
+## Pass OAuth Refresh token to upstream via "X-Forwarded-Refresh-Token"
+# pass_refresh_token = false
+
 ## Authenticated Email Addresses File (one email per line)
 # authenticated_emails_file = ""
 
diff --git a/docs/docs/configuration/overview.md b/docs/docs/configuration/overview.md
index b159df09..ca53ae21 100644
--- a/docs/docs/configuration/overview.md
+++ b/docs/docs/configuration/overview.md
@@ -143,6 +143,7 @@ Provider specific options can be found on their respective subpages.
 | flag: `--set-basic-auth`
toml: `set_basic_auth`                       | bool   | set HTTP Basic Auth information in response (useful in Nginx auth_request mode)                                                                                                                                                                                  | false   |
 | flag: `--skip-auth-strip-headers`
toml: `skip_auth_strip_headers`     | bool   | strips `X-Forwarded-*` style authentication headers & `Authorization` header if they would be set by oauth2-proxy                                                                                                                                                | true    |
 | flag: `--pass-access-token`
toml: `pass_access_token`                 | bool   | pass OAuth access_token to upstream via X-Forwarded-Access-Token header. When used with `--set-xauthrequest` this adds the X-Auth-Request-Access-Token header to the response                                                                                    | false   |
+| flag: `--pass-refresh-token`
toml: `pass_refresh_token`               | bool   | pass OAuth refresh_token to upstream via X-Forwarded-Refresh-Token header                                                                                                                                                                                        | false   |
 | flag: `--pass-authorization-header`
toml: `pass_authorization_header` | bool   | pass OIDC IDToken to upstream via Authorization Bearer header                                                                                                                                                                                                    | false   |
 | flag: `--pass-basic-auth`
toml: `pass_basic_auth`                     | bool   | pass HTTP Basic Auth, X-Forwarded-User, X-Forwarded-Email and X-Forwarded-Preferred-Username information to upstream                                                                                                                                             | true    |
 | flag: `--prefer-email-to-user`
toml: `prefer_email_to_user`           | bool   | Prefer to use the Email address as the Username when passing information to upstream. Will only use Username if Email is unavailable, e.g. htaccess authentication. Used in conjunction with `--pass-basic-auth` and `--pass-user-headers`                       | false   |
diff --git a/oauthproxy_test.go b/oauthproxy_test.go
index 488b8cea..ebda2ab3 100644
--- a/oauthproxy_test.go
+++ b/oauthproxy_test.go
@@ -99,6 +99,19 @@ func (tp *TestProvider) GetEmailAddress(_ context.Context, _ *sessions.SessionSt
 	return tp.EmailAddress, nil
 }
 
+func (tp *TestProvider) Redeem(ctx context.Context, redirectURL, code, codeVerifier string) (*sessions.SessionState, error) {
+	// Call the parent Redeem to get the basic session with access_token
+	session, err := tp.ProviderData.Redeem(ctx, redirectURL, code, codeVerifier)
+	if err != nil {
+		return nil, err
+	}
+
+	session.RefreshToken = "my_refresh_token"
+	session.IDToken = "my_id_token"
+
+	return session, nil
+}
+
 func (tp *TestProvider) ValidateSession(_ context.Context, _ *sessions.SessionState) bool {
 	return tp.ValidToken
 }
@@ -313,20 +326,22 @@ func TestPassGroupsHeadersWithGroups(t *testing.T) {
 	assert.Equal(t, []string{"a,b"}, req.Header["X-Forwarded-Groups"])
 }
 
-type PassAccessTokenTest struct {
+type PassTokensTest struct {
 	providerServer *httptest.Server
 	proxy          *OAuthProxy
 	opts           *options.Options
 }
 
-type PassAccessTokenTestOptions struct {
-	PassAccessToken bool
-	ValidToken      bool
-	ProxyUpstream   options.Upstream
+type PassTokensTestOptions struct {
+	PassAccessToken   bool
+	PassRefreshToken  bool
+	PassAuthorization bool
+	ValidToken        bool
+	ProxyUpstream     options.Upstream
 }
 
-func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTest, error) {
-	patt := &PassAccessTokenTest{}
+func NewPassTokensTest(opts PassTokensTestOptions) (*PassTokensTest, error) {
+	patt := &PassTokensTest{}
 
 	patt.providerServer = httptest.NewServer(
 		http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
@@ -334,6 +349,16 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
 			switch r.URL.Path {
 			case "/oauth/token":
 				payload = `{"access_token": "my_auth_token"}`
+			case "/refresh":
+				payload = r.Header.Get("X-Forwarded-Refresh-Token")
+				if payload == "" {
+					payload = "No refresh token found."
+				}
+			case "/authorization":
+				payload = r.Header.Get("Authorization")
+				if payload == "" {
+					payload = "No ID token found."
+				}
 			default:
 				payload = r.Header.Get("X-Forwarded-Access-Token")
 				if payload == "" {
@@ -362,21 +387,49 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
 	}
 
 	patt.opts.Cookie.Secure = false
+	headers := []options.Header{}
 	if opts.PassAccessToken {
-		patt.opts.InjectRequestHeaders = []options.Header{
-			{
-				Name: "X-Forwarded-Access-Token",
-				Values: []options.HeaderValue{
-					{
-						ClaimSource: &options.ClaimSource{
-							Claim: "access_token",
-						},
+		headers = append(headers, options.Header{
+			Name: "X-Forwarded-Access-Token",
+			Values: []options.HeaderValue{
+				{
+					ClaimSource: &options.ClaimSource{
+						Claim: "access_token",
 					},
 				},
 			},
-		}
+		})
 	}
 
+	if opts.PassRefreshToken {
+		headers = append(headers, options.Header{
+			Name: "X-Forwarded-Refresh-Token",
+			Values: []options.HeaderValue{
+				{
+					ClaimSource: &options.ClaimSource{
+						Claim: "refresh_token",
+					},
+				},
+			},
+		})
+	}
+
+	if opts.PassAuthorization {
+		headers = append(headers, options.Header{
+			Name: "Authorization",
+			Values: []options.HeaderValue{
+				{
+					ClaimSource: &options.ClaimSource{
+						Claim:  "id_token",
+						Prefix: "Bearer ",
+					},
+				},
+			},
+		})
+	}
+
+	patt.opts.InjectRequestHeaders = headers
+
 	err := validation.Validate(patt.opts)
 	if err != nil {
 		return nil, err
@@ -397,11 +450,11 @@ func NewPassAccessTokenTest(opts PassAccessTokenTestOptions) (*PassAccessTokenTe
 	return patt, nil
 }
 
-func (patTest *PassAccessTokenTest) Close() {
+func (patTest *PassTokensTest) Close() {
 	patTest.providerServer.Close()
 }
 
-func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie string) {
+func (patTest *PassTokensTest) getCallbackEndpoint() (httpCode int, cookie string) {
 	rw := httptest.NewRecorder()
 
 	csrf, err := cookies.NewCSRF(patTest.proxy.CookieOptions, "")
@@ -439,7 +492,7 @@ func (patTest *PassAccessTokenTest) getCallbackEndpoint() (httpCode int, cookie
 
 // getEndpointWithCookie makes a requests againt the oauthproxy with passed requestPath
 // and cookie and returns body and status code.
-func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
+func (patTest *PassTokensTest) getEndpointWithCookie(cookie string, endpoint string) (httpCode int, accessToken string) {
 	cookieName := patTest.proxy.CookieOptions.Name
 	var value string
 	keyPrefix := cookieName + "="
@@ -473,7 +526,7 @@ func (patTest *PassAccessTokenTest) getEndpointWithCookie(cookie string, endpoin
 }
 
 func TestForwardAccessTokenUpstream(t *testing.T) {
-	patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
 		PassAccessToken: true,
 		ValidToken:      true,
 	})
@@ -499,8 +552,64 @@ func TestForwardAccessTokenUpstream(t *testing.T) {
 	assert.Equal(t, "my_auth_token", payload)
 }
 
+func TestForwardRefreshTokenUpstream(t *testing.T) {
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
+		PassRefreshToken: true,
+		ValidToken:       true,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Cleanup(patTest.Close)
+
+	// A successful validation will redirect and set the auth cookie.
+	code, cookie := patTest.getCallbackEndpoint()
+	if code != 302 {
+		t.Fatalf("expected 302; got %d", code)
+	}
+	assert.NotNil(t, cookie)
+
+	// Now we make a regular request; the refresh_token from the cookie is
+	// forwarded as the "X-Forwarded-Refresh-Token" header. The token is
+	// read by the test provider server and written in the response body.
+	code, payload := patTest.getEndpointWithCookie(cookie, "/refresh")
+	if code != 200 {
+		t.Fatalf("expected 200; got %d", code)
+	}
+	assert.Equal(t, "my_refresh_token", payload)
+}
+
+func TestForwardIDTokenUpstream(t *testing.T) {
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
+		PassAuthorization: true,
+		PassAccessToken:   true,
+		PassRefreshToken:  true,
+		ValidToken:        true,
+	})
+	if err != nil {
+		t.Fatal(err)
+	}
+	t.Cleanup(patTest.Close)
+
+	// A successful validation will redirect and set the auth cookie.
+	code, cookie := patTest.getCallbackEndpoint()
+	if code != 302 {
+		t.Fatalf("expected 302; got %d", code)
+	}
+	assert.NotNil(t, cookie)
+
+	// Now we make a regular request; the id_token from the cookie is
+	// forwarded as the "Authorization" header with Bearer prefix. The token is
+	// read by the test provider server and written in the response body.
+	code, payload := patTest.getEndpointWithCookie(cookie, "/authorization")
+	if code != 200 {
+		t.Fatalf("expected 200; got %d", code)
+	}
+	assert.Equal(t, "Bearer my_id_token", payload)
+}
+
 func TestStaticProxyUpstream(t *testing.T) {
-	patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
 		PassAccessToken: true,
 		ValidToken:      true,
 		ProxyUpstream: options.Upstream{
@@ -531,7 +640,7 @@ func TestStaticProxyUpstream(t *testing.T) {
 }
 
 func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
-	patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
 		PassAccessToken: false,
 		ValidToken:      true,
 	})
@@ -557,7 +666,7 @@ func TestDoNotForwardAccessTokenUpstream(t *testing.T) {
 }
 
 func TestSessionValidationFailure(t *testing.T) {
-	patTest, err := NewPassAccessTokenTest(PassAccessTokenTestOptions{
+	patTest, err := NewPassTokensTest(PassTokensTestOptions{
 		ValidToken: false,
 	})
 	require.NoError(t, err)
diff --git a/pkg/apis/options/legacy_options.go b/pkg/apis/options/legacy_options.go
index 12975225..f082ce6c 100644
--- a/pkg/apis/options/legacy_options.go
+++ b/pkg/apis/options/legacy_options.go
@@ -194,6 +194,7 @@ func (l *LegacyUpstreams) convert() (UpstreamConfig, error) {
 type LegacyHeaders struct {
 	PassBasicAuth     bool `flag:"pass-basic-auth" cfg:"pass_basic_auth"`
 	PassAccessToken   bool `flag:"pass-access-token" cfg:"pass_access_token"`
+	PassRefreshToken  bool `flag:"pass-refresh-token" cfg:"pass_refresh_token"`
 	PassUserHeaders   bool `flag:"pass-user-headers" cfg:"pass_user_headers"`
 	PassAuthorization bool `flag:"pass-authorization-header" cfg:"pass_authorization_header"`
 
@@ -211,6 +212,7 @@ func legacyHeadersFlagSet() *pflag.FlagSet {
 
 	flagSet.Bool("pass-basic-auth", true, "pass HTTP Basic Auth, X-Forwarded-User and X-Forwarded-Email information to upstream")
 	flagSet.Bool("pass-access-token", false, "pass OAuth access_token to upstream via X-Forwarded-Access-Token header")
+	flagSet.Bool("pass-refresh-token", false, "pass OAuth refresh_token to upstream via X-Forwarded-Refresh-Token header")
 	flagSet.Bool("pass-user-headers", true, "pass X-Forwarded-User and X-Forwarded-Email information to upstream")
 	flagSet.Bool("pass-authorization-header", false, "pass the Authorization Header to upstream")
 
@@ -248,6 +250,10 @@ func (l *LegacyHeaders) getRequestHeaders() []Header {
 		requestHeaders = append(requestHeaders, getPassAccessTokenHeader())
 	}
 
+	if l.PassRefreshToken {
+		requestHeaders = append(requestHeaders, getPassRefreshTokenHeader())
+	}
+
 	if l.PassAuthorization {
 		requestHeaders = append(requestHeaders, getAuthorizationHeader())
 	}
@@ -368,6 +374,19 @@ func getPassAccessTokenHeader() Header {
 	}
 }
 
+func getPassRefreshTokenHeader() Header {
+	return Header{
+		Name: "X-Forwarded-Refresh-Token",
+		Values: []HeaderValue{
+			{
+				ClaimSource: &ClaimSource{
+					Claim: "refresh_token",
+				},
+			},
+		},
+	}
+}
+
 func getAuthorizationHeader() Header {
 	return Header{
 		Name: "Authorization",
diff --git a/pkg/apis/options/legacy_options_test.go b/pkg/apis/options/legacy_options_test.go
index 9481cf95..b73907b9 100644
--- a/pkg/apis/options/legacy_options_test.go
+++ b/pkg/apis/options/legacy_options_test.go
@@ -400,6 +400,18 @@ var _ = Describe("Legacy Options", func() {
 			},
 		}
 
+		xForwardedRefreshToken := Header{
+			Name:                 "X-Forwarded-Refresh-Token",
+			PreserveRequestValue: false,
+			Values: []HeaderValue{
+				{
+					ClaimSource: &ClaimSource{
+						Claim: "refresh_token",
+					},
+				},
+			},
+		}
+
 		basicAuthHeaderWithEmail := Header{
 			Name:                 "Authorization",
 			PreserveRequestValue: false,
@@ -499,6 +511,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -517,6 +530,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     true,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -543,6 +557,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     true,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -569,6 +584,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     true,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -594,6 +610,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     true,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   true,
 					PassAuthorization: false,
 
@@ -620,6 +637,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   true,
 					PassAuthorization: false,
 
@@ -643,6 +661,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   true,
 					PassAuthorization: false,
 
@@ -666,6 +685,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -689,6 +709,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   true,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -709,6 +730,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   true,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -735,6 +757,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   true,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: false,
 
@@ -751,10 +774,32 @@ var _ = Describe("Legacy Options", func() {
 				},
 				expectedResponseHeaders: []Header{},
 			}),
+			Entry("with passRefreshToken", legacyHeadersTableInput{
+				legacyHeaders: &LegacyHeaders{
+					PassBasicAuth:     false,
+					PassAccessToken:   false,
+					PassRefreshToken:  true,
+					PassUserHeaders:   false,
+					PassAuthorization: false,
+
+					SetBasicAuth:     false,
+					SetXAuthRequest:  false,
+					SetAuthorization: false,
+
+					PreferEmailToUser:    false,
+					BasicAuthPassword:    "",
+					SkipAuthStripHeaders: true,
+				},
+				expectedRequestHeaders: []Header{
+					xForwardedRefreshToken,
+				},
+				expectedResponseHeaders: []Header{},
+			}),
 			Entry("with authorization headers", legacyHeadersTableInput{
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: true,
 
@@ -777,6 +822,7 @@ var _ = Describe("Legacy Options", func() {
 				legacyHeaders: &LegacyHeaders{
 					PassBasicAuth:     false,
 					PassAccessToken:   false,
+					PassRefreshToken:  false,
 					PassUserHeaders:   false,
 					PassAuthorization: true,