authenticate: handle XHR redirect flow (#387)

- authenticate: add cors preflight check support for sign_in endpoint
- internal/httputil: indicate responses that originate from pomerium vs the app
- proxy: detect XHR requests and do not redirect on failure.
- authenticate: removed default session duration; should be maintained out of band with rpc.
This commit is contained in:
Bobby DeSimone 2019-11-14 19:37:31 -08:00 committed by GitHub
parent 9030bd32cb
commit 00c29f4e77
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
11 changed files with 128 additions and 35 deletions

View file

@ -10,8 +10,9 @@ import (
"strings"
"time"
"github.com/pomerium/csrf"
"github.com/rs/cors"
"github.com/pomerium/csrf"
"github.com/pomerium/pomerium/internal/cryptutil"
"github.com/pomerium/pomerium/internal/httputil"
"github.com/pomerium/pomerium/internal/log"
@ -51,6 +52,14 @@ func (a *Authenticate) Handler() http.Handler {
// Proxy service endpoints
v := r.PathPrefix("/.pomerium").Subrouter()
c := cors.New(cors.Options{
AllowOriginRequestFunc: func(r *http.Request, _ string) bool {
return middleware.ValidateRedirectURI(r, a.sharedKey)
},
AllowCredentials: true,
AllowedHeaders: []string{"*"},
})
v.Use(c.Handler)
v.Use(middleware.ValidateSignature(a.sharedKey))
v.Use(sessions.RetrieveSession(a.sessionLoaders...))
v.Use(a.VerifySession)
@ -73,15 +82,15 @@ func (a *Authenticate) VerifySession(next http.Handler) http.Handler {
if errors.Is(err, sessions.ErrExpired) {
if err := a.refresh(w, r, state); err != nil {
log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session, refresh")
a.redirectToIdentityProvider(w, r)
a.reauthenticateOrFail(w, r, err)
return
}
// redirect to restart middleware-chain following refresh
http.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound)
httputil.Redirect(w, r, urlutil.GetAbsoluteURL(r).String(), http.StatusFound)
return
} else if err != nil {
log.FromRequest(r).Info().Err(err).Msg("authenticate: verify session")
a.redirectToIdentityProvider(w, r)
a.reauthenticateOrFail(w, r, err)
return
}
next.ServeHTTP(w, r)
@ -167,7 +176,7 @@ func (a *Authenticate) SignIn(w http.ResponseWriter, r *http.Request) {
// build our hmac-d redirect URL with our session, pointing back to the
// proxy's callback URL which is responsible for setting our new route-session
uri := urlutil.SignedRedirectURL(a.sharedKey, callbackURL, redirectURL)
http.Redirect(w, r, uri.String(), http.StatusFound)
httputil.Redirect(w, r, uri.String(), http.StatusFound)
}
// SignOut signs the user out and attempts to revoke the user's identity session
@ -189,16 +198,25 @@ func (a *Authenticate) SignOut(w http.ResponseWriter, r *http.Request) {
httputil.ErrorResponse(w, r, httputil.Error("malformed redirect_uri", http.StatusBadRequest, err))
return
}
http.Redirect(w, r, redirectURL.String(), http.StatusFound)
httputil.Redirect(w, r, redirectURL.String(), http.StatusFound)
}
// redirectToIdentityProvider starts the authenticate process by redirecting the
// reauthenticateOrFail starts the authenticate process by redirecting the
// user to their respective identity provider. This function also builds the
// 'state' parameter which is encrypted and includes authenticating data
// for validation.
// If the request is a `xhr/ajax` request (e.g the `X-Requested-With` header)
// is set do not redirect but instead return 401 unauthorized.
//
// https://openid.net/specs/openid-connect-core-1_0-final.html#AuthRequest
// https://tools.ietf.org/html/rfc6749#section-4.2.1
func (a *Authenticate) redirectToIdentityProvider(w http.ResponseWriter, r *http.Request) {
// https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest
func (a *Authenticate) reauthenticateOrFail(w http.ResponseWriter, r *http.Request, err error) {
// If request AJAX/XHR request, return a 401 instead .
if reqType := r.Header.Get("X-Requested-With"); strings.EqualFold(reqType, "XmlHttpRequest") {
httputil.ErrorResponse(w, r, httputil.Error(err.Error(), http.StatusUnauthorized, err))
return
}
a.sessionStore.ClearSession(w, r)
redirectURL := a.RedirectURL.ResolveReference(r.URL)
nonce := csrf.Token(r)
@ -207,7 +225,7 @@ func (a *Authenticate) redirectToIdentityProvider(w http.ResponseWriter, r *http
enc := cryptutil.Encrypt(a.cookieCipher, []byte(redirectURL.String()), b)
b = append(b, enc...)
encodedState := base64.URLEncoding.EncodeToString(b)
http.Redirect(w, r, a.provider.GetSignInURL(encodedState), http.StatusFound)
httputil.Redirect(w, r, a.provider.GetSignInURL(encodedState), http.StatusFound)
}
// OAuthCallback handles the callback from the identity provider.
@ -220,7 +238,7 @@ func (a *Authenticate) OAuthCallback(w http.ResponseWriter, r *http.Request) {
httputil.ErrorResponse(w, r, fmt.Errorf("oauth callback : %w", err))
return
}
http.Redirect(w, r, redirect.String(), http.StatusFound)
httputil.Redirect(w, r, redirect.String(), http.StatusFound)
}
func (a *Authenticate) getOAuthCallback(w http.ResponseWriter, r *http.Request) (*url.URL, error) {