@@ -24,9 +24,9 @@ import (
24 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
24 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/ip"
25 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
25 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/logger"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
26 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/middleware"
27+ requestutil "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/requests/util"
27 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions"
28 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/sessions"
28 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
29 "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/upstream"
29− "github.com/oauth2-proxy/oauth2-proxy/v7/pkg/util"
30 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
30 "github.com/oauth2-proxy/oauth2-proxy/v7/providers"
@@ -98,7 +98,6 @@ type OAuthProxy struct {
98 SetAuthorization bool
98 SetAuthorization bool
99 PassAuthorization bool
99 PassAuthorization bool
100 PreferEmailToUser bool
100 PreferEmailToUser bool
102 skipAuthPreflight bool
101 skipAuthPreflight bool
103 skipJwtBearerTokens bool
102 skipJwtBearerTokens bool
104 templates *template.Template
103 templates *template.Template
@@ -201,7 +200,6 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
201 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
200 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
203 ProxyPrefix: opts.ProxyPrefix,
202 ProxyPrefix: opts.ProxyPrefix,
204− ReverseProxy: opts.ReverseProxy,
205 provider: opts.GetProvider(),
203 provider: opts.GetProvider(),
206 providerNameOverride: opts.ProviderName,
204 providerNameOverride: opts.ProviderName,
207 sessionStore: sessionStore,
205 sessionStore: sessionStore,
@@ -231,7 +229,7 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
231// the OAuth2 Proxy authentication logic kicks in.
229// the OAuth2 Proxy authentication logic kicks in.
232// For example forcing HTTPS or health checks.
230// For example forcing HTTPS or health checks.
233func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
231func buildPreAuthChain(opts *options.Options) (alice.Chain, error) {
234− chain := alice.New(middleware.NewScope(opts))
232+ chain := alice.New(middleware.NewScope(opts.ReverseProxy))
236 if opts.ForceHTTPS {
234 if opts.ForceHTTPS {
237 _, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
235 _, httpsPort, err := net.SplitHostPort(opts.HTTPSAddress)
@@ -368,9 +366,9 @@ func buildRoutesAllowlist(opts *options.Options) ([]allowedRoute, error) {
368 return routes, nil
366 return routes, nil
371−// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
369+// GetOAuthRedirectURI returns the redirectURL that the upstream OAuth Provider will
372// redirect clients to once authenticated
370// redirect clients to once authenticated
373−func (p *OAuthProxy) GetRedirectURI(host string) string {
371+func (p *OAuthProxy) GetOAuthRedirectURI(host string) string {
374 // default to the request Host if not set
372 // default to the request Host if not set
375 if p.redirectURL.Host != "" {
373 if p.redirectURL.Host != "" {
376 return p.redirectURL.String()
374 return p.redirectURL.String()
@@ -391,7 +389,7 @@ func (p *OAuthProxy) redeemCode(ctx context.Context, host, code string) (*sessio
391 if code == "" {
389 if code == "" {
392 return nil, providers.ErrMissingCode
390 return nil, providers.ErrMissingCode
394− redirectURI := p.GetRedirectURI(host)
392+ redirectURI := p.GetOAuthRedirectURI(host)
395 s, err := p.provider.Redeem(ctx, redirectURI, code)
393 s, err := p.provider.Redeem(ctx, redirectURI, code)
396 if err != nil {
394 if err != nil {
397 return nil, err
395 return nil, err
@@ -420,7 +418,7 @@ func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, ex
420 cookieDomain := cookies.GetCookieDomain(req, p.CookieDomains)
418 cookieDomain := cookies.GetCookieDomain(req, p.CookieDomains)
422 if cookieDomain != "" {
420 if cookieDomain != "" {
423− domain := util.GetRequestHost(req)
421+ domain := requestutil.GetRequestHost(req)
424 if h, _, err := net.SplitHostPort(domain); err == nil {
422 if h, _, err := net.SplitHostPort(domain); err == nil {
425 domain = h
423 domain = h
@@ -509,7 +507,7 @@ func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code
510 rw.WriteHeader(code)
508 rw.WriteHeader(code)
512− redirectURL, err := p.GetRedirect(req)
510+ redirectURL, err := p.GetAppRedirect(req)
513 if err != nil {
511 if err != nil {
514 logger.Errorf("Error obtaining redirect: %v", err)
512 logger.Errorf("Error obtaining redirect: %v", err)
515 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
513 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
@@ -568,46 +566,108 @@ func (p *OAuthProxy) ManualSignIn(req *http.Request) (string, bool) {
568 return "", false
566 return "", false
571−// GetRedirect reads the query parameter to get the URL to redirect clients to
569+// GetAppRedirect determines the full URL or URI path to redirect clients to
572// once authenticated with the OAuthProxy
570// once authenticated with the OAuthProxy
571+// Strategy priority (first legal result is used):
572+// - `rd` querysting parameter
573+// - `X-Auth-Request-Redirect` header
574+// - `X-Forwarded-(Proto|Host|Uri)` headers (when ReverseProxy mode is enabled)
575+// - `X-Forwarded-(Proto|Host)` if `Uri` has the ProxyPath (i.e. /oauth2/*)
576+// - `X-Forwarded-Uri` direct URI path (when ReverseProxy mode is enabled)
577+// - `req.URL.RequestURI` if not under the ProxyPath (i.e. /oauth2/*)
573−func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
579+func (p *OAuthProxy) GetAppRedirect(req *http.Request) (string, error) {
574− err = req.ParseForm()
580+ err := req.ParseForm()
575 if err != nil {
581 if err != nil {
576− return
582+ return "", err
585+ // These redirect getter functions are strategies ordered by priority
586+ // for figuring out the redirect URL.
587+ type redirectGetter func(req *http.Request) string
588+ for _, rdGetter := range []redirectGetter{
589+ p.getRdQuerystringRedirect,
590+ p.getXAuthRequestRedirect,
591+ p.getXForwardedHeadersRedirect,
594+ if redirect := rdGetter(req); redirect != "" {
595+ return redirect, nil
579− redirect = req.Header.Get("X-Auth-Request-Redirect")
599+ return "/", nil
580− if req.Form.Get("rd") != "" {
581− redirect = req.Form.Get("rd")
583− // Quirk: On reverse proxies that doesn't have support for
584− // "X-Auth-Request-Redirect" header or dynamic header/query string
585− // manipulation (like Traefik v1 and v2), we can try if the header
586− // X-Forwarded-Host exists or not.
587− if redirect == "" && isForwardedRequest(req, p.ReverseProxy) {
588− redirect = p.getRedirectFromForwardHeaders(req)
590− if !p.IsValidRedirect(redirect) {
591− // Use RequestURI to preserve ?query
592− redirect = req.URL.RequestURI()
602+func isForwardedRequest(req *http.Request) bool {
603+ return requestutil.IsProxied(req) &&
604+ req.Host != requestutil.GetRequestHost(req)
607+func (p *OAuthProxy) hasProxyPrefix(path string) bool {
594− if strings.HasPrefix(redirect, fmt.Sprintf("%s/", p.ProxyPrefix)) {
608+ return strings.HasPrefix(path, fmt.Sprintf("%s/", p.ProxyPrefix))
611+// getRdQuerystringRedirect handles this GetAppRedirect strategy:
612+// - `rd` querysting parameter
613+func (p *OAuthProxy) getRdQuerystringRedirect(req *http.Request) string {
595− redirect = "/"
614+ redirect := req.Form.Get("rd")
596− }
615+ if p.IsValidRedirect(redirect) {
621+// getXAuthRequestRedirect handles this GetAppRedirect strategy:
622+// - `X-Auth-Request-Redirect` Header
623+func (p *OAuthProxy) getXAuthRequestRedirect(req *http.Request) string {
624+ redirect := req.Header.Get("X-Auth-Request-Redirect")
625+ if p.IsValidRedirect(redirect) {
599− return
628+ return ""
631+// getXForwardedHeadersRedirect handles these GetAppRedirect strategies:
602−// getRedirectFromForwardHeaders returns the redirect URL based on X-Forwarded-{Proto,Host,Uri} headers
632+// - `X-Forwarded-(Proto|Host|Uri)` headers (when ReverseProxy mode is enabled)
633+// - `X-Forwarded-(Proto|Host)` if `Uri` has the ProxyPath (i.e. /oauth2/*)
603−func (p *OAuthProxy) getRedirectFromForwardHeaders(req *http.Request) string {
634+func (p *OAuthProxy) getXForwardedHeadersRedirect(req *http.Request) string {
604− uri := util.GetRequestURI(req)
635+ if !isForwardedRequest(req) {
606− if strings.HasPrefix(uri, fmt.Sprintf("%s/", p.ProxyPrefix)) {
639+ uri := requestutil.GetRequestURI(req)
640+ if p.hasProxyPrefix(uri) {
607 uri = "/"
641 uri = "/"
610− return fmt.Sprintf("%s://%s%s", util.GetRequestProto(req), util.GetRequestHost(req), uri)
644+ redirect := fmt.Sprintf(
646+ requestutil.GetRequestProto(req),
647+ requestutil.GetRequestHost(req),
651+ if p.IsValidRedirect(redirect) {
657+// getURIRedirect handles these GetAppRedirect strategies:
658+// - `X-Forwarded-Uri` direct URI path (when ReverseProxy mode is enabled)
659+// - `req.URL.RequestURI` if not under the ProxyPath (i.e. /oauth2/*)
661+func (p *OAuthProxy) getURIRedirect(req *http.Request) string {
662+ redirect := requestutil.GetRequestURI(req)
663+ if !p.IsValidRedirect(redirect) {
664+ redirect = req.URL.RequestURI()
667+ if p.hasProxyPrefix(redirect) {
613// splitHostPort separates host and port. If the port is not valid, it returns
673// splitHostPort separates host and port. If the port is not valid, it returns
@@ -707,12 +767,6 @@ func (p *OAuthProxy) isAllowedRoute(req *http.Request) bool {
707 return false
767 return false
710−// isForwardedRequest is used to check if X-Forwarded-Host header exists or not
711−func isForwardedRequest(req *http.Request, reverseProxy bool) bool {
712− isForwarded := req.Host != util.GetRequestHost(req)
713− return isForwarded && reverseProxy
716// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
770// See https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/http-caching?hl=en
717var noCacheHeaders = map[string]string{
771var noCacheHeaders = map[string]string{
718 "Expires": time.Unix(0, 0).Format(time.RFC1123),
772 "Expires": time.Unix(0, 0).Format(time.RFC1123),
@@ -781,7 +835,7 @@ func (p *OAuthProxy) serveHTTP(rw http.ResponseWriter, req *http.Request) {
782// SignIn serves a page prompting users to sign in
836// SignIn serves a page prompting users to sign in
783func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
837func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
784− redirect, err := p.GetRedirect(req)
838+ redirect, err := p.GetAppRedirect(req)
785 if err != nil {
839 if err != nil {
786 logger.Errorf("Error obtaining redirect: %v", err)
840 logger.Errorf("Error obtaining redirect: %v", err)
787 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
841 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
@@ -839,7 +893,7 @@ func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
840// SignOut sends a response to clear the authentication cookie
894// SignOut sends a response to clear the authentication cookie
841func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
895func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
842− redirect, err := p.GetRedirect(req)
896+ redirect, err := p.GetAppRedirect(req)
843 if err != nil {
897 if err != nil {
844 logger.Errorf("Error obtaining redirect: %v", err)
898 logger.Errorf("Error obtaining redirect: %v", err)
845 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
899 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
@@ -864,13 +918,13 @@ func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
866 p.SetCSRFCookie(rw, req, nonce)
920 p.SetCSRFCookie(rw, req, nonce)
867− redirect, err := p.GetRedirect(req)
921+ redirect, err := p.GetAppRedirect(req)
868 if err != nil {
922 if err != nil {
869 logger.Errorf("Error obtaining redirect: %v", err)
923 logger.Errorf("Error obtaining redirect: %v", err)
870 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
924 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", err.Error())
873− redirectURI := p.GetRedirectURI(util.GetRequestHost(req))
927+ redirectURI := p.GetOAuthRedirectURI(requestutil.GetRequestHost(req))
874 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), http.StatusFound)
928 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), http.StatusFound)
@@ -893,7 +947,7 @@ func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
896− session, err := p.redeemCode(req.Context(), util.GetRequestHost(req), req.Form.Get("code"))
950+ session, err := p.redeemCode(req.Context(), requestutil.GetRequestHost(req), req.Form.Get("code"))
897 if err != nil {
951 if err != nil {
898 logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
952 logger.Errorf("Error redeeming code during OAuth2 callback: %v", err)
899 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
953 p.ErrorPage(rw, http.StatusInternalServerError, "Internal Server Error", "Internal Error")
@@ -1024,7 +1078,7 @@ func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.R
1024 var session *sessionsapi.SessionState
1078 var session *sessionsapi.SessionState
1026 getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
1080 getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
1027− session = middleware.GetRequestScope(req).Session
1081+ session = middlewareapi.GetRequestScope(req).Session
1029 getSession.ServeHTTP(rw, req)
1083 getSession.ServeHTTP(rw, req)