@@ -15,7 +15,9 @@ import (
1515 "time"
1616
1717 "github.com/coreos/go-oidc"
18+ "github.com/justinas/alice"
1819 ipapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/ip"
20+ middlewareapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/middleware"
1921 "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/options"
2022 sessionsapi "github.com/oauth2-proxy/oauth2-proxy/pkg/apis/sessions"
2123 "github.com/oauth2-proxy/oauth2-proxy/pkg/authentication/basic"
@@ -23,6 +25,7 @@ import (
2325 "github.com/oauth2-proxy/oauth2-proxy/pkg/encryption"
2426 "github.com/oauth2-proxy/oauth2-proxy/pkg/ip"
2527 "github.com/oauth2-proxy/oauth2-proxy/pkg/logger"
28+ "github.com/oauth2-proxy/oauth2-proxy/pkg/middleware"
2629 "github.com/oauth2-proxy/oauth2-proxy/pkg/sessions"
2730 "github.com/oauth2-proxy/oauth2-proxy/pkg/upstream"
2831 "github.com/oauth2-proxy/oauth2-proxy/providers"
@@ -98,6 +101,8 @@ type OAuthProxy struct {
98101 trustedIPs *ip.NetSet
99102 Banner string
100103 Footer string
104+
105+ sessionChain alice.Chain
101106}
102107
103108// NewOAuthProxy creates a new instance of OAuthProxy from the options provided
@@ -156,6 +161,8 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
156161 }
157162 }
158163
164+ sessionChain := buildSessionChain(opts, sessionStore, basicAuthValidator)
165+
159166 return &OAuthProxy{
160167 CookieName: opts.Cookie.Name,
161168 CSRFCookieName: fmt.Sprintf("%v_%v", opts.Cookie.Name, "csrf"),
@@ -209,9 +216,45 @@ func NewOAuthProxy(opts *options.Options, validator func(string) bool) (*OAuthPr
209216
210217 basicAuthValidator: basicAuthValidator,
211218 displayHtpasswdForm: basicAuthValidator != nil,
219+ sessionChain: sessionChain,
212220 }, nil
213221}
214222
223+func buildSessionChain(opts *options.Options, sessionStore sessionsapi.SessionStore, validator basic.Validator) alice.Chain {
224+ chain := alice.New(middleware.NewScope())
225+
226+ if opts.SkipJwtBearerTokens {
227+ sessionLoaders := []middlewareapi.TokenToSessionLoader{}
228+ if opts.GetOIDCVerifier() != nil {
229+ sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
230+ Verifier: opts.GetOIDCVerifier(),
231+ TokenToSession: opts.GetProvider().CreateSessionStateFromBearerToken,
232+ })
233+ }
234+
235+ for _, verifier := range opts.GetJWTBearerVerifiers() {
236+ sessionLoaders = append(sessionLoaders, middlewareapi.TokenToSessionLoader{
237+ Verifier: verifier,
238+ })
239+ }
240+
241+ chain = chain.Append(middleware.NewJwtSessionLoader(sessionLoaders))
242+ }
243+
244+ if validator != nil {
245+ chain = chain.Append(middleware.NewBasicAuthSessionLoader(validator))
246+ }
247+
248+ chain = chain.Append(middleware.NewStoredSessionLoader(&middleware.StoredSessionLoaderOptions{
249+ SessionStore: sessionStore,
250+ RefreshPeriod: opts.Cookie.Refresh,
251+ RefreshSessionIfNeeded: opts.GetProvider().RefreshSessionIfNeeded,
252+ ValidateSessionState: opts.GetProvider().ValidateSessionState,
253+ }))
254+
255+ return chain
256+}
257+
215258// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
216259// redirect clients to once authenticated
217260func (p *OAuthProxy) GetRedirectURI(host string) string {
@@ -780,86 +823,20 @@ func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
780823// Set-Cookie headers may be set on the response as a side-effect of calling this method.
781824func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) {
782825 var session *sessionsapi.SessionState
783− var err error
784− var saveSession, clearSession, revalidated bool
785826
786− if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" {
787− session, err = p.GetJwtSession(req)
788− if err != nil {
789− logger.Printf("Error retrieving session from token in Authorization header: %s", err)
790− }
791− if session != nil {
792− saveSession = false
793− }
794− }
827+ getSession := p.sessionChain.Then(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
828+ session = middleware.GetRequestScope(req).Session
829+ }))
830+ getSession.ServeHTTP(rw, req)
795831
796− remoteAddr := ip.GetClientString(p.realClientIPParser, req, true)
797832 if session == nil {
798− session, err = p.LoadCookiedSession(req)
799− if err != nil {
800− logger.Printf("Error loading cookied session: %s", err)
801− }
802−
803− if session != nil {
804− if session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
805− logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
806− saveSession = true
807− }
808−
809− if ok, err := p.provider.RefreshSessionIfNeeded(req.Context(), session); err != nil {
810− logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
811− clearSession = true
812− session = nil
813− } else if ok {
814− saveSession = true
815− revalidated = true
816− }
817− }
818− }
819−
820− if session != nil && session.IsExpired() {
821− logger.Printf("Removing session: token expired %s", session)
822− session = nil
823− saveSession = false
824− clearSession = true
825− }
826−
827− if saveSession && !revalidated && session != nil && session.AccessToken != "" {
828− if !p.provider.ValidateSessionState(req.Context(), session) {
829− logger.Printf("Removing session: error validating %s", session)
830− saveSession = false
831− session = nil
832− clearSession = true
833− }
833+ return nil, ErrNeedsLogin
834834 }
835835
836836 if session != nil && session.Email != "" && !p.Validator(session.Email) {
837837 logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
838− session = nil
839− saveSession = false
840− clearSession = true
841− }
842−
843− if saveSession && session != nil {
844− err = p.SaveSession(rw, req, session)
845− if err != nil {
846− logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
847− return nil, err
848− }
849− }
850−
851− if clearSession {
838+ // Invalid session, clear it
852839 p.ClearSessionCookie(rw, req)
853− }
854−
855− if session == nil {
856− session, err = p.CheckBasicAuth(req)
857− if err != nil {
858− logger.Printf("Error during basic auth validation: %s", err)
859− }
860− }
861−
862− if session == nil {
863840 return nil, ErrNeedsLogin
864841 }
865842
@@ -997,36 +974,6 @@ func (p *OAuthProxy) stripAuthHeaders(req *http.Request) {
997974 }
998975}
999976
1000−// CheckBasicAuth checks the requests Authorization header for basic auth
1001−// credentials and authenticates these against the proxies HtpasswdFile
1002−func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
1003− if p.basicAuthValidator == nil {
1004− return nil, nil
1005− }
1006− auth := req.Header.Get("Authorization")
1007− if auth == "" {
1008− return nil, nil
1009− }
1010− s := strings.SplitN(auth, " ", 2)
1011− if len(s) != 2 || s[0] != "Basic" {
1012− return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
1013− }
1014− b, err := b64.StdEncoding.DecodeString(s[1])
1015− if err != nil {
1016− return nil, err
1017− }
1018− pair := strings.SplitN(string(b), ":", 2)
1019− if len(pair) != 2 {
1020− return nil, fmt.Errorf("invalid format %s", b)
1021− }
1022− if p.basicAuthValidator.Validate(pair[0], pair[1]) {
1023− logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
1024− return &sessionsapi.SessionState{User: pair[0]}, nil
1025− }
1026− logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
1027− return nil, nil
1028−}
1029−
1030977// isAjax checks if a request is an ajax request
1031978func isAjax(req *http.Request) bool {
1032979 acceptValues := req.Header.Values("Accept")
@@ -1044,74 +991,3 @@ func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
1044991 rw.Header().Set("Content-Type", applicationJSON)
1045992 rw.WriteHeader(code)
1046993}
1047−
1048−// GetJwtSession loads a session based on a JWT token in the authorization header.
1049−// (see the config options skip-jwt-bearer-tokens and extra-jwt-issuers)
1050−func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState, error) {
1051− rawBearerToken, err := p.findBearerToken(req)
1052− if err != nil {
1053− return nil, err
1054− }
1055−
1056− // If we are using an oidc provider, go ahead and try that provider first with its Verifier
1057− // and Bearer Token -> Session converter
1058− if p.mainJwtBearerVerifier != nil {
1059− bearerToken, err := p.mainJwtBearerVerifier.Verify(req.Context(), rawBearerToken)
1060− if err == nil {
1061− return p.provider.CreateSessionStateFromBearerToken(req.Context(), rawBearerToken, bearerToken)
1062− }
1063− }
1064−
1065− // Otherwise, attempt to verify against the extra JWT issuers and use a more generic
1066− // Bearer Token -> Session converter
1067− for _, verifier := range p.extraJwtBearerVerifiers {
1068− bearerToken, err := verifier.Verify(req.Context(), rawBearerToken)
1069− if err != nil {
1070− continue
1071− }
1072−
1073− return (*providers.ProviderData)(nil).CreateSessionStateFromBearerToken(req.Context(), rawBearerToken, bearerToken)
1074− }
1075− return nil, fmt.Errorf("unable to verify jwt token %s", req.Header.Get("Authorization"))
1076−}
1077−
1078−// findBearerToken finds a valid JWT token from the Authorization header of a given request.
1079−func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) {
1080− auth := req.Header.Get("Authorization")
1081− s := strings.SplitN(auth, " ", 2)
1082− if len(s) != 2 {
1083− return "", fmt.Errorf("invalid authorization header %s", auth)
1084− }
1085− jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`)
1086− var rawBearerToken string
1087− if s[0] == "Bearer" && jwtRegex.MatchString(s[1]) {
1088− rawBearerToken = s[1]
1089− } else if s[0] == "Basic" {
1090− // Check if we have a Bearer token masquerading in Basic
1091− b, err := b64.StdEncoding.DecodeString(s[1])
1092− if err != nil {
1093− return "", err
1094− }
1095− pair := strings.SplitN(string(b), ":", 2)
1096− if len(pair) != 2 {
1097− return "", fmt.Errorf("invalid format %s", b)
1098− }
1099− user, password := pair[0], pair[1]
1100−
1101− // check user, user+password, or just password for a token
1102− if jwtRegex.MatchString(user) {
1103− // Support blank passwords or magic `x-oauth-basic` passwords - nothing else
1104− if password == "" || password == "x-oauth-basic" {
1105− rawBearerToken = user
1106− }
1107− } else if jwtRegex.MatchString(password) {
1108− // support passwords and ignore user
1109− rawBearerToken = password
1110− }
1111− }
1112− if rawBearerToken == "" {
1113− return "", fmt.Errorf("no valid bearer token found in authorization header")
1114− }
1115−
1116− return rawBearerToken, nil
1117−}