Files
authentik/internal/outpost/proxyv2/application/oauth_state.go
Jens L c45bb8e985 providers/proxy: rework redirect mechanism (#8594)
* providers/proxy: rework redirect mechanism

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* add session id, don't tie to state in session

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* handle state failing to parse

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* save session after creating state

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* remove debug

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* include task expiry in status

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix redirect URL detection

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

* fix tests

Signed-off-by: Jens Langhammer <jens@goauthentik.io>

---------

Signed-off-by: Jens Langhammer <jens@goauthentik.io>
2024-05-06 03:07:08 +02:00

96 lines
3.3 KiB
Go

package application
import (
"encoding/base32"
"encoding/base64"
"fmt"
"net/http"
"github.com/golang-jwt/jwt/v5"
"github.com/gorilla/securecookie"
"github.com/mitchellh/mapstructure"
)
type OAuthState struct {
Issuer string `json:"iss" mapstructure:"iss"`
SessionID string `json:"sid" mapstructure:"sid"`
State string `json:"state" mapstructure:"state"`
Redirect string `json:"redirect" mapstructure:"redirect"`
}
func (oas *OAuthState) GetExpirationTime() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetIssuedAt() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetNotBefore() (*jwt.NumericDate, error) { return nil, nil }
func (oas *OAuthState) GetIssuer() (string, error) { return oas.Issuer, nil }
func (oas *OAuthState) GetSubject() (string, error) { return oas.State, nil }
func (oas *OAuthState) GetAudience() (jwt.ClaimStrings, error) { return nil, nil }
var base32RawStdEncoding = base32.StdEncoding.WithPadding(base32.NoPadding)
func (a *Application) createState(r *http.Request, fwd string) (string, error) {
s, _ := a.sessions.Get(r, a.SessionName())
if s.ID == "" {
// Ensure session has an ID
s.ID = base32RawStdEncoding.EncodeToString(securecookie.GenerateRandomKey(32))
}
st := &OAuthState{
Issuer: fmt.Sprintf("goauthentik.io/outpost/%s", a.proxyConfig.GetClientId()),
State: base64.RawURLEncoding.EncodeToString(securecookie.GenerateRandomKey(32)),
SessionID: s.ID,
Redirect: fwd,
}
if fwd == "" {
// This should only really be hit for nginx forward_auth
// as for that the auth start redirect URL is generated by the
// reverse proxy, and as such we won't have a request we just
// denied to reference for final URL
rd, ok := a.checkRedirectParam(r)
if ok {
a.log.WithField("rd", rd).Trace("Setting redirect")
st.Redirect = rd
}
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, st)
tokenString, err := token.SignedString([]byte(a.proxyConfig.GetCookieSecret()))
if err != nil {
return "", err
}
return tokenString, nil
}
func (a *Application) stateFromRequest(r *http.Request) *OAuthState {
stateJwt := r.URL.Query().Get("state")
token, err := jwt.Parse(stateJwt, func(token *jwt.Token) (interface{}, error) {
// Don't forget to validate the alg is what you expect:
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
}
return []byte(a.proxyConfig.GetCookieSecret()), nil
})
if err != nil {
a.log.WithError(err).Warning("failed to parse state jwt")
return nil
}
iss, err := token.Claims.GetIssuer()
if err != nil {
a.log.WithError(err).Warning("state jwt without issuer")
return nil
}
if iss != fmt.Sprintf("goauthentik.io/outpost/%s", a.proxyConfig.GetClientId()) {
a.log.WithField("issuer", iss).Warning("invalid state jwt issuer")
return nil
}
claims := &OAuthState{}
err = mapstructure.Decode(token.Claims, &claims)
if err != nil {
a.log.WithError(err).Warning("failed to mapdecode")
return nil
}
s, _ := a.sessions.Get(r, a.SessionName())
if claims.SessionID != s.ID {
a.log.WithField("is", claims.SessionID).WithField("should", s.ID).Warning("mismatched session ID")
return nil
}
return claims
}