mirror of
https://github.com/pomerium/pomerium.git
synced 2025-05-09 15:17:39 +02:00
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:
parent
9030bd32cb
commit
00c29f4e77
11 changed files with 128 additions and 35 deletions
|
@ -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) {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue