20 "github.com/coreos/go-oidc"
21 "github.com/mbland/hmacauth"
22 sessionsapi "github.com/pusher/oauth2_proxy/pkg/apis/sessions"
23 "github.com/pusher/oauth2_proxy/pkg/encryption"
24 "github.com/pusher/oauth2_proxy/pkg/logger"
25 "github.com/pusher/oauth2_proxy/providers"
26 "github.com/yhat/wsutil"
30 // SignatureHeader is the name of the request header containing the GAP Signature
32 SignatureHeader = "GAP-Signature"
37 applicationJSON = "application/json"
40// SignatureHeaders contains the headers to be signed by the hmac algorithm
42var SignatureHeaders = []string{
50 "X-Forwarded-Access-Token",
56 // ErrNeedsLogin means the user should be redirected to the login page
57 ErrNeedsLogin = errors.New("redirect to login page")
60// OAuthProxy is the main authentication proxy
61type OAuthProxy struct {
69 CookieExpire time.Duration
70 CookieRefresh time.Duration
71 Validator func(string) bool
78 OAuthCallbackPath string
82 redirectURL *url.URL // the url to receive requests at
83 whitelistDomains []string
84 provider providers.Provider
85 providerNameOverride string
86 sessionStore sessionsapi.SessionStore
89 HtpasswdFile *HtpasswdFile
90 DisplayHtpasswdForm bool
94 SkipProviderButton bool
96 BasicAuthPassword string
99 PassAuthorization bool
100 skipAuthRegex []string
101 skipAuthPreflight bool
102 skipJwtBearerTokens bool
103 jwtBearerVerifiers []*oidc.IDTokenVerifier
104 compiledRegex []*regexp.Regexp
105 templates *template.Template
110// UpstreamProxy represents an upstream server to proxy to
111type UpstreamProxy struct {
114 wsHandler http.Handler
115 auth hmacauth.HmacAuth
118// ServeHTTP proxies requests to the upstream provider while signing the
120func (u *UpstreamProxy) ServeHTTP(w http.ResponseWriter, r *http.Request) {
121 w.Header().Set("GAP-Upstream-Address", u.upstream)
123 r.Header.Set("GAP-Auth", w.Header().Get("GAP-Auth"))
124 u.auth.SignRequest(r)
126 if u.wsHandler != nil && strings.EqualFold(r.Header.Get("Connection"), "upgrade") && r.Header.Get("Upgrade") == "websocket" {
127 u.wsHandler.ServeHTTP(w, r)
129 u.handler.ServeHTTP(w, r)
134// NewReverseProxy creates a new reverse proxy for proxying requests to upstream
136func NewReverseProxy(target *url.URL, opts *Options) (proxy *httputil.ReverseProxy) {
137 proxy = httputil.NewSingleHostReverseProxy(target)
138 proxy.FlushInterval = opts.FlushInterval
139 if opts.SSLUpstreamInsecureSkipVerify {
140 proxy.Transport = &http.Transport{
141 TLSClientConfig: &tls.Config{InsecureSkipVerify: true},
147func setProxyUpstreamHostHeader(proxy *httputil.ReverseProxy, target *url.URL) {
148 director := proxy.Director
149 proxy.Director = func(req *http.Request) {
151 // use RequestURI so that we aren't unescaping encoded slashes in the request path
152 req.Host = target.Host
153 req.URL.Opaque = req.RequestURI
154 req.URL.RawQuery = ""
158func setProxyDirector(proxy *httputil.ReverseProxy) {
159 director := proxy.Director
160 proxy.Director = func(req *http.Request) {
162 // use RequestURI so that we aren't unescaping encoded slashes in the request path
163 req.URL.Opaque = req.RequestURI
164 req.URL.RawQuery = ""
168// NewFileServer creates a http.Handler to serve files from the filesystem
169func NewFileServer(path string, filesystemPath string) (proxy http.Handler) {
170 return http.StripPrefix(path, http.FileServer(http.Dir(filesystemPath)))
173// NewWebSocketOrRestReverseProxy creates a reverse proxy for REST or websocket based on url
174func NewWebSocketOrRestReverseProxy(u *url.URL, opts *Options, auth hmacauth.HmacAuth) http.Handler {
176 proxy := NewReverseProxy(u, opts)
177 if !opts.PassHostHeader {
178 setProxyUpstreamHostHeader(proxy, u)
180 setProxyDirector(proxy)
183 // this should give us a wss:// scheme if the url is https:// based.
184 var wsProxy *wsutil.ReverseProxy
185 if opts.ProxyWebSockets {
186 wsScheme := "ws" + strings.TrimPrefix(u.Scheme, "http")
187 wsURL := &url.URL{Scheme: wsScheme, Host: u.Host}
188 wsProxy = wsutil.NewSingleHostReverseProxy(wsURL)
190 return &UpstreamProxy{
198// NewOAuthProxy creates a new instance of OOuthProxy from the options provided
199func NewOAuthProxy(opts *Options, validator func(string) bool) *OAuthProxy {
200 serveMux := http.NewServeMux()
201 var auth hmacauth.HmacAuth
202 if sigData := opts.signatureData; sigData != nil {
203 auth = hmacauth.NewHmacAuth(sigData.hash, []byte(sigData.key),
204 SignatureHeader, SignatureHeaders)
206 for _, u := range opts.proxyURLs {
210 case httpScheme, httpsScheme:
211 logger.Printf("mapping path %q => upstream %q", path, u)
212 proxy := NewWebSocketOrRestReverseProxy(u, opts, auth)
213 serveMux.Handle(path, proxy)
215 responseCode, err := strconv.Atoi(host)
217 logger.Printf("unable to convert %q to int, use default \"200\"", host)
221 serveMux.HandleFunc(path, func(rw http.ResponseWriter, req *http.Request) {
222 rw.WriteHeader(responseCode)
223 fmt.Fprintf(rw, "Authenticated")
226 if u.Fragment != "" {
229 logger.Printf("mapping path %q => file system %q", path, u.Path)
230 proxy := NewFileServer(path, u.Path)
231 uProxy := UpstreamProxy{
237 serveMux.Handle(path, &uProxy)
239 panic(fmt.Sprintf("unknown upstream protocol %s", u.Scheme))
242 for _, u := range opts.CompiledRegex {
243 logger.Printf("compiled skip-auth-regex => %q", u)
246 if opts.SkipJwtBearerTokens {
247 logger.Printf("Skipping JWT tokens from configured OIDC issuer: %q", opts.OIDCIssuerURL)
248 for _, issuer := range opts.ExtraJwtIssuers {
249 logger.Printf("Skipping JWT tokens from extra JWT issuer: %q", issuer)
252 redirectURL := opts.redirectURL
253 if redirectURL.Path == "" {
254 redirectURL.Path = fmt.Sprintf("%s/callback", opts.ProxyPrefix)
257 logger.Printf("OAuthProxy configured for %s Client ID: %s", opts.provider.Data().ProviderName, opts.ClientID)
258 refresh := "disabled"
259 if opts.CookieRefresh != time.Duration(0) {
260 refresh = fmt.Sprintf("after %s", opts.CookieRefresh)
263 logger.Printf("Cookie settings: name:%s secure(https):%v httponly:%v expiry:%s domain:%s path:%s refresh:%s", opts.CookieName, opts.CookieSecure, opts.CookieHTTPOnly, opts.CookieExpire, opts.CookieDomain, opts.CookiePath, refresh)
266 CookieName: opts.CookieName,
267 CSRFCookieName: fmt.Sprintf("%v_%v", opts.CookieName, "csrf"),
268 CookieSeed: opts.CookieSecret,
269 CookieDomain: opts.CookieDomain,
270 CookiePath: opts.CookiePath,
271 CookieSecure: opts.CookieSecure,
272 CookieHTTPOnly: opts.CookieHTTPOnly,
273 CookieExpire: opts.CookieExpire,
274 CookieRefresh: opts.CookieRefresh,
275 Validator: validator,
277 RobotsPath: "/robots.txt",
278 PingPath: opts.PingPath,
279 SignInPath: fmt.Sprintf("%s/sign_in", opts.ProxyPrefix),
280 SignOutPath: fmt.Sprintf("%s/sign_out", opts.ProxyPrefix),
281 OAuthStartPath: fmt.Sprintf("%s/start", opts.ProxyPrefix),
282 OAuthCallbackPath: fmt.Sprintf("%s/callback", opts.ProxyPrefix),
283 AuthOnlyPath: fmt.Sprintf("%s/auth", opts.ProxyPrefix),
284 UserInfoPath: fmt.Sprintf("%s/userinfo", opts.ProxyPrefix),
286 ProxyPrefix: opts.ProxyPrefix,
287 provider: opts.provider,
288 providerNameOverride: opts.ProviderName,
289 sessionStore: opts.sessionStore,
291 redirectURL: redirectURL,
292 whitelistDomains: opts.WhitelistDomains,
293 skipAuthRegex: opts.SkipAuthRegex,
294 skipAuthPreflight: opts.SkipAuthPreflight,
295 skipJwtBearerTokens: opts.SkipJwtBearerTokens,
296 jwtBearerVerifiers: opts.jwtBearerVerifiers,
297 compiledRegex: opts.CompiledRegex,
298 SetXAuthRequest: opts.SetXAuthRequest,
299 PassBasicAuth: opts.PassBasicAuth,
300 PassUserHeaders: opts.PassUserHeaders,
301 BasicAuthPassword: opts.BasicAuthPassword,
302 PassAccessToken: opts.PassAccessToken,
303 SetAuthorization: opts.SetAuthorization,
304 PassAuthorization: opts.PassAuthorization,
305 SkipProviderButton: opts.SkipProviderButton,
306 templates: loadTemplates(opts.CustomTemplatesDir),
312// GetRedirectURI returns the redirectURL that the upstream OAuth Provider will
313// redirect clients to once authenticated
314func (p *OAuthProxy) GetRedirectURI(host string) string {
315 // default to the request Host if not set
316 if p.redirectURL.Host != "" {
317 return p.redirectURL.String()
323 u.Scheme = httpsScheme
325 u.Scheme = httpScheme
332func (p *OAuthProxy) displayCustomLoginForm() bool {
333 return p.HtpasswdFile != nil && p.DisplayHtpasswdForm
336func (p *OAuthProxy) redeemCode(host, code string) (s *sessionsapi.SessionState, err error) {
338 return nil, errors.New("missing code")
340 redirectURI := p.GetRedirectURI(host)
341 s, err = p.provider.Redeem(redirectURI, code)
347 s.Email, err = p.provider.GetEmailAddress(s)
351 s.User, err = p.provider.GetUserName(s)
352 if err != nil && err.Error() == "not implemented" {
359// MakeCSRFCookie creates a cookie for CSRF
360func (p *OAuthProxy) MakeCSRFCookie(req *http.Request, value string, expiration time.Duration, now time.Time) *http.Cookie {
361 return p.makeCookie(req, p.CSRFCookieName, value, expiration, now)
364func (p *OAuthProxy) makeCookie(req *http.Request, name string, value string, expiration time.Duration, now time.Time) *http.Cookie {
365 if p.CookieDomain != "" {
367 if h, _, err := net.SplitHostPort(domain); err == nil {
370 if !strings.HasSuffix(domain, p.CookieDomain) {
371 logger.Printf("Warning: request host is %q but using configured cookie domain of %q", domain, p.CookieDomain)
379 Domain: p.CookieDomain,
380 HttpOnly: p.CookieHTTPOnly,
381 Secure: p.CookieSecure,
382 Expires: now.Add(expiration),
386// ClearCSRFCookie creates a cookie to unset the CSRF cookie stored in the user's
388func (p *OAuthProxy) ClearCSRFCookie(rw http.ResponseWriter, req *http.Request) {
389 http.SetCookie(rw, p.MakeCSRFCookie(req, "", time.Hour*-1, time.Now()))
392// SetCSRFCookie adds a CSRF cookie to the response
393func (p *OAuthProxy) SetCSRFCookie(rw http.ResponseWriter, req *http.Request, val string) {
394 http.SetCookie(rw, p.MakeCSRFCookie(req, val, p.CookieExpire, time.Now()))
397// ClearSessionCookie creates a cookie to unset the user's authentication cookie
398// stored in the user's session
399func (p *OAuthProxy) ClearSessionCookie(rw http.ResponseWriter, req *http.Request) error {
400 return p.sessionStore.Clear(rw, req)
403// LoadCookiedSession reads the user's authentication details from the request
404func (p *OAuthProxy) LoadCookiedSession(req *http.Request) (*sessionsapi.SessionState, error) {
405 return p.sessionStore.Load(req)
408// SaveSession creates a new session cookie value and sets this on the response
409func (p *OAuthProxy) SaveSession(rw http.ResponseWriter, req *http.Request, s *sessionsapi.SessionState) error {
410 return p.sessionStore.Save(rw, req, s)
413// RobotsTxt disallows scraping pages from the OAuthProxy
414func (p *OAuthProxy) RobotsTxt(rw http.ResponseWriter) {
415 rw.WriteHeader(http.StatusOK)
416 fmt.Fprintf(rw, "User-agent: *\nDisallow: /")
419// PingPage responds 200 OK to requests
420func (p *OAuthProxy) PingPage(rw http.ResponseWriter) {
421 rw.WriteHeader(http.StatusOK)
422 fmt.Fprintf(rw, "OK")
425// ErrorPage writes an error response
426func (p *OAuthProxy) ErrorPage(rw http.ResponseWriter, code int, title string, message string) {
433 Title: fmt.Sprintf("%d %s", code, title),
435 ProxyPrefix: p.ProxyPrefix,
437 p.templates.ExecuteTemplate(rw, "error.html", t)
440// SignInPage writes the sing in template to the response
441func (p *OAuthProxy) SignInPage(rw http.ResponseWriter, req *http.Request, code int) {
442 p.ClearSessionCookie(rw, req)
445 redirecURL := req.URL.RequestURI()
446 if req.Header.Get("X-Auth-Request-Redirect") != "" {
447 redirecURL = req.Header.Get("X-Auth-Request-Redirect")
449 if redirecURL == p.SignInPath {
462 ProviderName: p.provider.Data().ProviderName,
463 SignInMessage: p.SignInMessage,
464 CustomLogin: p.displayCustomLoginForm(),
465 Redirect: redirecURL,
467 ProxyPrefix: p.ProxyPrefix,
468 Footer: template.HTML(p.Footer),
470 if p.providerNameOverride != "" {
471 t.ProviderName = p.providerNameOverride
473 p.templates.ExecuteTemplate(rw, "sign_in.html", t)
476// ManualSignIn handles basic auth logins to the proxy
477func (p *OAuthProxy) ManualSignIn(rw http.ResponseWriter, req *http.Request) (string, bool) {
478 if req.Method != "POST" || p.HtpasswdFile == nil {
481 user := req.FormValue("username")
482 passwd := req.FormValue("password")
487 if p.HtpasswdFile.Validate(user, passwd) {
488 logger.PrintAuthf(user, req, logger.AuthSuccess, "Authenticated via HtpasswdFile")
491 logger.PrintAuthf(user, req, logger.AuthFailure, "Invalid authentication via HtpasswdFile")
495// GetRedirect reads the query parameter to get the URL to redirect clients to
496// once authenticated with the OAuthProxy
497func (p *OAuthProxy) GetRedirect(req *http.Request) (redirect string, err error) {
498 err = req.ParseForm()
503 redirect = req.Header.Get("X-Auth-Request-Redirect")
504 if req.Form.Get("rd") != "" {
505 redirect = req.Form.Get("rd")
507 if !p.IsValidRedirect(redirect) {
508 redirect = req.URL.Path
509 if strings.HasPrefix(redirect, p.ProxyPrefix) {
517// IsValidRedirect checks whether the redirect URL is whitelisted
518func (p *OAuthProxy) IsValidRedirect(redirect string) bool {
520 case strings.HasPrefix(redirect, "/") && !strings.HasPrefix(redirect, "//"):
522 case strings.HasPrefix(redirect, "http://") || strings.HasPrefix(redirect, "https://"):
523 redirectURL, err := url.Parse(redirect)
527 for _, domain := range p.whitelistDomains {
528 if (redirectURL.Host == domain) || (strings.HasPrefix(domain, ".") && strings.HasSuffix(redirectURL.Host, domain)) {
538// IsWhitelistedRequest is used to check if auth should be skipped for this request
539func (p *OAuthProxy) IsWhitelistedRequest(req *http.Request) bool {
540 isPreflightRequestAllowed := p.skipAuthPreflight && req.Method == "OPTIONS"
541 return isPreflightRequestAllowed || p.IsWhitelistedPath(req.URL.Path)
544// IsWhitelistedPath is used to check if the request path is allowed without auth
545func (p *OAuthProxy) IsWhitelistedPath(path string) bool {
546 for _, u := range p.compiledRegex {
547 if u.MatchString(path) {
554func getRemoteAddr(req *http.Request) (s string) {
556 if req.Header.Get("X-Real-IP") != "" {
557 s += fmt.Sprintf(" (%q)", req.Header.Get("X-Real-IP"))
562func (p *OAuthProxy) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
563 switch path := req.URL.Path; {
564 case path == p.RobotsPath:
566 case path == p.PingPath:
568 case p.IsWhitelistedRequest(req):
569 p.serveMux.ServeHTTP(rw, req)
570 case path == p.SignInPath:
572 case path == p.SignOutPath:
574 case path == p.OAuthStartPath:
575 p.OAuthStart(rw, req)
576 case path == p.OAuthCallbackPath:
577 p.OAuthCallback(rw, req)
578 case path == p.AuthOnlyPath:
579 p.AuthenticateOnly(rw, req)
580 case path == p.UserInfoPath:
587// SignIn serves a page prompting users to sign in
588func (p *OAuthProxy) SignIn(rw http.ResponseWriter, req *http.Request) {
589 redirect, err := p.GetRedirect(req)
591 logger.Printf("Error obtaining redirect: %s", err.Error())
592 p.ErrorPage(rw, 500, "Internal Error", err.Error())
596 user, ok := p.ManualSignIn(rw, req)
598 session := &sessionsapi.SessionState{User: user}
599 p.SaveSession(rw, req, session)
600 http.Redirect(rw, req, redirect, 302)
602 if p.SkipProviderButton {
603 p.OAuthStart(rw, req)
605 p.SignInPage(rw, req, http.StatusOK)
610//UserInfo endpoint outputs session email in JSON format
611func (p *OAuthProxy) UserInfo(rw http.ResponseWriter, req *http.Request) {
613 session, err := p.getAuthenticatedSession(rw, req)
615 http.Error(rw, http.StatusText(http.StatusUnauthorized), http.StatusUnauthorized)
619 Email string `json:"email"`
621 rw.Header().Set("Content-Type", "application/json")
622 rw.WriteHeader(http.StatusOK)
623 json.NewEncoder(rw).Encode(userInfo)
626// SignOut sends a response to clear the authentication cookie
627func (p *OAuthProxy) SignOut(rw http.ResponseWriter, req *http.Request) {
628 redirect, err := p.GetRedirect(req)
630 logger.Printf("Error obtaining redirect: %s", err.Error())
631 p.ErrorPage(rw, 500, "Internal Error", err.Error())
634 p.ClearSessionCookie(rw, req)
635 http.Redirect(rw, req, redirect, 302)
638// OAuthStart starts the OAuth2 authentication flow
639func (p *OAuthProxy) OAuthStart(rw http.ResponseWriter, req *http.Request) {
640 nonce, err := encryption.Nonce()
642 logger.Printf("Error obtaining nonce: %s", err.Error())
643 p.ErrorPage(rw, 500, "Internal Error", err.Error())
646 p.SetCSRFCookie(rw, req, nonce)
647 redirect, err := p.GetRedirect(req)
649 logger.Printf("Error obtaining redirect: %s", err.Error())
650 p.ErrorPage(rw, 500, "Internal Error", err.Error())
653 redirectURI := p.GetRedirectURI(req.Host)
654 http.Redirect(rw, req, p.provider.GetLoginURL(redirectURI, fmt.Sprintf("%v:%v", nonce, redirect)), 302)
657// OAuthCallback is the OAuth2 authentication flow callback that finishes the
658// OAuth2 authentication flow
659func (p *OAuthProxy) OAuthCallback(rw http.ResponseWriter, req *http.Request) {
660 remoteAddr := getRemoteAddr(req)
662 // finish the oauth cycle
663 err := req.ParseForm()
665 logger.Printf("Error while parsing OAuth2 callback: %s" + err.Error())
666 p.ErrorPage(rw, 500, "Internal Error", err.Error())
669 errorString := req.Form.Get("error")
670 if errorString != "" {
671 logger.Printf("Error while parsing OAuth2 callback: %s ", errorString)
672 p.ErrorPage(rw, 403, "Permission Denied", errorString)
676 session, err := p.redeemCode(req.Host, req.Form.Get("code"))
678 logger.Printf("Error redeeming code during OAuth2 callback: %s ", err.Error())
679 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
683 s := strings.SplitN(req.Form.Get("state"), ":", 2)
685 logger.Printf("Error while parsing OAuth2 state: invalid length")
686 p.ErrorPage(rw, 500, "Internal Error", "Invalid State")
691 c, err := req.Cookie(p.CSRFCookieName)
693 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unable too obtain CSRF cookie")
694 p.ErrorPage(rw, 403, "Permission Denied", err.Error())
697 p.ClearCSRFCookie(rw, req)
698 if c.Value != nonce {
699 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: csrf token mismatch, potential attack")
700 p.ErrorPage(rw, 403, "Permission Denied", "csrf failed")
704 if !p.IsValidRedirect(redirect) {
708 // set cookie, or deny
709 if p.Validator(session.Email) && p.provider.ValidateGroup(session.Email) {
710 logger.PrintAuthf(session.Email, req, logger.AuthSuccess, "Authenticated via OAuth2: %s", session)
711 err := p.SaveSession(rw, req, session)
713 logger.Printf("%s %s", remoteAddr, err)
714 p.ErrorPage(rw, 500, "Internal Error", "Internal Error")
717 http.Redirect(rw, req, redirect, 302)
719 logger.PrintAuthf(session.Email, req, logger.AuthFailure, "Invalid authentication via OAuth2: unauthorized")
720 p.ErrorPage(rw, 403, "Permission Denied", "Invalid Account")
724// AuthenticateOnly checks whether the user is currently logged in
725func (p *OAuthProxy) AuthenticateOnly(rw http.ResponseWriter, req *http.Request) {
726 session, err := p.getAuthenticatedSession(rw, req)
728 http.Error(rw, "unauthorized request", http.StatusUnauthorized)
732 // we are authenticated
733 p.addHeadersForProxying(rw, req, session)
734 rw.WriteHeader(http.StatusAccepted)
737// Proxy proxies the user request if the user is authenticated else it prompts
738// them to authenticate
739func (p *OAuthProxy) Proxy(rw http.ResponseWriter, req *http.Request) {
740 session, err := p.getAuthenticatedSession(rw, req)
743 // we are authenticated
744 p.addHeadersForProxying(rw, req, session)
745 p.serveMux.ServeHTTP(rw, req)
748 // we need to send the user to a login screen
750 // no point redirecting an AJAX request
751 p.ErrorJSON(rw, http.StatusUnauthorized)
755 if p.SkipProviderButton {
756 p.OAuthStart(rw, req)
758 p.SignInPage(rw, req, http.StatusForbidden)
763 logger.Printf("Unexpected internal error: %s", err)
764 p.ErrorPage(rw, http.StatusInternalServerError,
765 "Internal Error", "Internal Error")
770// getAuthenticatedSession checks whether a user is authenticated and returns a session object and nil error if so
771// Returns nil, ErrNeedsLogin if user needs to login.
772// Set-Cookie headers may be set on the response as a side-effect of calling this method.
773func (p *OAuthProxy) getAuthenticatedSession(rw http.ResponseWriter, req *http.Request) (*sessionsapi.SessionState, error) {
774 var session *sessionsapi.SessionState
776 var saveSession, clearSession, revalidated bool
778 if p.skipJwtBearerTokens && req.Header.Get("Authorization") != "" {
779 session, err = p.GetJwtSession(req)
781 logger.Printf("Error retrieving session from token in Authorization header: %s", err)
788 remoteAddr := getRemoteAddr(req)
790 session, err = p.LoadCookiedSession(req)
792 logger.Printf("Error loading cookied session: %s", err)
796 if session.Age() > p.CookieRefresh && p.CookieRefresh != time.Duration(0) {
797 logger.Printf("Refreshing %s old session cookie for %s (refresh after %s)", session.Age(), session, p.CookieRefresh)
801 if ok, err := p.provider.RefreshSessionIfNeeded(session); err != nil {
802 logger.Printf("%s removing session. error refreshing access token %s %s", remoteAddr, err, session)
812 if session != nil && session.IsExpired() {
813 logger.Printf("Removing session: token expired %s", session)
819 if saveSession && !revalidated && session != nil && session.AccessToken != "" {
820 if !p.provider.ValidateSessionState(session) {
821 logger.Printf("Removing session: error validating %s", session)
828 if session != nil && session.Email != "" {
829 if !p.Validator(session.Email) || !p.provider.ValidateGroup(session.Email) {
830 logger.Printf(session.Email, req, logger.AuthFailure, "Invalid authentication via session: removing session %s", session)
837 if saveSession && session != nil {
838 err = p.SaveSession(rw, req, session)
840 logger.PrintAuthf(session.Email, req, logger.AuthError, "Save session error %s", err)
846 p.ClearSessionCookie(rw, req)
850 session, err = p.CheckBasicAuth(req)
852 logger.Printf("Error during basic auth validation: %s", err)
857 return nil, ErrNeedsLogin
863// addHeadersForProxying adds the appropriate headers the request / response for proxying
864func (p *OAuthProxy) addHeadersForProxying(rw http.ResponseWriter, req *http.Request, session *sessionsapi.SessionState) {
866 req.SetBasicAuth(session.User, p.BasicAuthPassword)
867 req.Header["X-Forwarded-User"] = []string{session.User}
868 if session.Email != "" {
869 req.Header["X-Forwarded-Email"] = []string{session.Email}
871 req.Header.Del("X-Forwarded-Email")
875 if p.PassUserHeaders {
876 req.Header["X-Forwarded-User"] = []string{session.User}
877 if session.Email != "" {
878 req.Header["X-Forwarded-Email"] = []string{session.Email}
880 req.Header.Del("X-Forwarded-Email")
884 if p.SetXAuthRequest {
885 rw.Header().Set("X-Auth-Request-User", session.User)
886 if session.Email != "" {
887 rw.Header().Set("X-Auth-Request-Email", session.Email)
889 rw.Header().Del("X-Auth-Request-Email")
892 if p.PassAccessToken {
893 if session.AccessToken != "" {
894 rw.Header().Set("X-Auth-Request-Access-Token", session.AccessToken)
896 rw.Header().Del("X-Auth-Request-Access-Token")
901 if p.PassAccessToken {
902 if session.AccessToken != "" {
903 req.Header["X-Forwarded-Access-Token"] = []string{session.AccessToken}
905 req.Header.Del("X-Forwarded-Access-Token")
909 if p.PassAuthorization {
910 if session.IDToken != "" {
911 req.Header["Authorization"] = []string{fmt.Sprintf("Bearer %s", session.IDToken)}
913 req.Header.Del("Authorization")
916 if p.SetAuthorization {
917 if session.IDToken != "" {
918 rw.Header().Set("Authorization", fmt.Sprintf("Bearer %s", session.IDToken))
920 rw.Header().Del("Authorization")
924 if session.Email == "" {
925 rw.Header().Set("GAP-Auth", session.User)
927 rw.Header().Set("GAP-Auth", session.Email)
931// CheckBasicAuth checks the requests Authorization header for basic auth
932// credentials and authenticates these against the proxies HtpasswdFile
933func (p *OAuthProxy) CheckBasicAuth(req *http.Request) (*sessionsapi.SessionState, error) {
934 if p.HtpasswdFile == nil {
937 auth := req.Header.Get("Authorization")
941 s := strings.SplitN(auth, " ", 2)
942 if len(s) != 2 || s[0] != "Basic" {
943 return nil, fmt.Errorf("invalid Authorization header %s", req.Header.Get("Authorization"))
945 b, err := b64.StdEncoding.DecodeString(s[1])
949 pair := strings.SplitN(string(b), ":", 2)
951 return nil, fmt.Errorf("invalid format %s", b)
953 if p.HtpasswdFile.Validate(pair[0], pair[1]) {
954 logger.PrintAuthf(pair[0], req, logger.AuthSuccess, "Authenticated via basic auth and HTpasswd File")
955 return &sessionsapi.SessionState{User: pair[0]}, nil
957 logger.PrintAuthf(pair[0], req, logger.AuthFailure, "Invalid authentication via basic auth: not in Htpasswd File")
961// isAjax checks if a request is an ajax request
962func isAjax(req *http.Request) bool {
963 acceptValues, ok := req.Header["accept"]
965 acceptValues = req.Header["Accept"]
967 const ajaxReq = applicationJSON
968 for _, v := range acceptValues {
976// ErrorJSON returns the error code with an application/json mime type
977func (p *OAuthProxy) ErrorJSON(rw http.ResponseWriter, code int) {
978 rw.Header().Set("Content-Type", applicationJSON)
982// GetJwtSession loads a session based on a JWT token in the authorization header.
983func (p *OAuthProxy) GetJwtSession(req *http.Request) (*sessionsapi.SessionState, error) {
984 rawBearerToken, err := p.findBearerToken(req)
989 ctx := context.Background()
990 var session *sessionsapi.SessionState
991 for _, verifier := range p.jwtBearerVerifiers {
992 bearerToken, err := verifier.Verify(ctx, rawBearerToken)
995 logger.Printf("failed to verify bearer token: %v", err)
1000 Subject string `json:"sub"`
1001 Email string `json:"email"`
1002 Verified *bool `json:"email_verified"`
1005 if err := bearerToken.Claims(&claims); err != nil {
1006 return nil, fmt.Errorf("failed to parse bearer token claims: %v", err)
1009 if claims.Email == "" {
1010 claims.Email = claims.Subject
1013 if claims.Verified != nil && !*claims.Verified {
1014 return nil, fmt.Errorf("email in id_token (%s) isn't verified", claims.Email)
1017 session = &sessionsapi.SessionState{
1018 AccessToken: rawBearerToken,
1019 IDToken: rawBearerToken,
1021 ExpiresOn: bearerToken.Expiry,
1022 Email: claims.Email,
1027 return nil, fmt.Errorf("unable to verify jwt token %s", req.Header.Get("Authorization"))
1030// findBearerToken finds a valid JWT token from the Authorization header of a given request.
1031func (p *OAuthProxy) findBearerToken(req *http.Request) (string, error) {
1032 auth := req.Header.Get("Authorization")
1033 s := strings.SplitN(auth, " ", 2)
1035 return "", fmt.Errorf("invalid authorization header %s", auth)
1037 jwtRegex := regexp.MustCompile(`^eyJ[a-zA-Z0-9_-]*\.eyJ[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`)
1038 var rawBearerToken string
1039 if s[0] == "Bearer" && jwtRegex.MatchString(s[1]) {
1040 rawBearerToken = s[1]
1041 } else if s[0] == "Basic" {
1042 // Check if we have a Bearer token masquerading in Basic
1043 b, err := b64.StdEncoding.DecodeString(s[1])
1047 pair := strings.SplitN(string(b), ":", 2)
1049 return "", fmt.Errorf("invalid format %s", b)
1051 user, password := pair[0], pair[1]
1053 // check user, user+password, or just password for a token
1054 if jwtRegex.MatchString(user) {
1055 // Support blank passwords or magic `x-oauth-basic` passwords - nothing else
1056 if password == "" || password == "x-oauth-basic" {
1057 rawBearerToken = user
1059 } else if jwtRegex.MatchString(password) {
1060 // support passwords and ignore user
1061 rawBearerToken = password
1064 if rawBearerToken == "" {
1065 return "", fmt.Errorf("no valid bearer token found in authorization header")
1068 return rawBearerToken, nil
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list