Skip to content

Commit

Permalink
feat: Support host + path based routing
Browse files Browse the repository at this point in the history
Create StateFunc type with relativeURL + schemeAndHost strategies for
forwarding requests. The value of SESSION_DOMAIN is used to pick the
strategy. If SESSION_DOMAIN is set then AuthService will be able to
serve requests for all subdomains of SESSION_DOMAIN and the host + path
of the request will be used when saving the oidc state for the request.

This commit adds 2 new env vars:
2. SCHEME_DEFAULT - default request scheme
3. SCHEME_HEADER - header to get original request scheme from
  • Loading branch information
asetty committed Dec 9, 2020
1 parent 6332719 commit cc31227
Show file tree
Hide file tree
Showing 4 changed files with 49 additions and 4 deletions.
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ Session store-related settings:
| `OIDC_STATE_STORE_PATH` | "/var/lib/authservice/oidc_state.db" | Path to the session store used to save the sessions for the OIDC state parameter. |
| `SESSION_MAX_AGE` | "86400" | Time in seconds after which sessions expire. Defaults to a day (24h). |
| `SESSION_SAME_SITE` | "Lax" | SameSite attribute of the session cookie. Check details of SameSite attribute [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie/SameSite). Its value can be "None", "Lax" or "Strict". |
| `SESSION_DOMAIN` | "" | Domain attribute of the session cookie. Check details of Domain attribute [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie) |
| `SESSION_DOMAIN` | "" | Domain attribute of the session cookie. Check details of Domain attribute [here](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Set-Cookie). If len(SESSION_DOMAIN) > 0 the incoming request's host and scheme are also saved in the state rather than just the path. This enables AuthService to service all subdomains of SESSION_DOMAIN. |
| `SCHEME_DEFAULT` | `https` | Default scheme for incoming requests. |
| `SCHEME_HEADER` | `<empty>` | Header to use for incoming request scheme. If ommitted or header is not present in request, SCHEME_DEFAULT will be used instead. |

By default, the AuthService keeps sessions to check if a user is authenticated. However, there may be times where
we want to check a user's logged in status at the Provider, effectively making the Provider the one keeping the
Expand Down
1 change: 1 addition & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func main() {
// TODO: Add support for Redis
store: store,
oidcStateStore: oidcStateStore,
stateFunc: NewStateFunc(c),
afterLoginRedirectURL: c.AfterLoginURL.String(),
homepageURL: c.HomepageURL.String(),
afterLogoutRedirectURL: c.AfterLogoutURL.String(),
Expand Down
3 changes: 2 additions & 1 deletion server.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ type server struct {
upstreamHTTPHeaderOpts httpHeaderOpts
caBundle []byte
sessionSameSite http.SameSite
stateFunc StateFunc
}

// jwtClaimOpts specifies the location of the user's identity inside a JWT's
Expand Down Expand Up @@ -142,7 +143,7 @@ func (s *server) authCodeFlowAuthenticationRequest(w http.ResponseWriter, r *htt
logger := loggerForRequest(r)

// Initiate OIDC Flow with Authorization Request.
state, err := createState(r, w, s.oidcStateStore)
state, err := createState(r, w, s.oidcStateStore, s.stateFunc)
if err != nil {
logger.Errorf("Failed to save state in store: %v", err)
returnMessage(w, http.StatusInternalServerError, "Failed to save state in store.")
Expand Down
45 changes: 43 additions & 2 deletions state.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@ package main
import (
"encoding/gob"
"net/http"
"strings"
"time"

"github.com/gorilla/sessions"
"github.com/pkg/errors"
log "github.com/sirupsen/logrus"
)

const (
Expand All @@ -20,6 +22,13 @@ func init() {
gob.Register(State{})
}

// TODO move state code to its own package
// type Config struct {
// SchemeDefault string
// SchemeHeader string
// SessionDomain string
// }

type State struct {
// FirstVisitedURL is the URL that the user visited when we redirected them
// to login.
Expand All @@ -32,14 +41,46 @@ func newState(firstVisitedURL string) *State {
}
}

type StateFunc func(*http.Request) *State

func NewStateFunc(config *config) StateFunc {
if len(config.SessionDomain) > 0 {
return newSchemeAndHost(config)
}
return relativeURL
}

func relativeURL(r *http.Request) *State {
return &state{
FirstVisitedURL: r.URL.String(),
}
}

func newSchemeAndHost(config *config) StateFunc {
return func(r *http.Request) *State {
// Use header value if it exists
s := r.Header.Get(config.SchemeHeader)
if s == "" {
s = config.SchemeDefault
}
// XXX Could return an error here. Would require changing the StateFunc type
if !strings.HasSuffix(r.Host, config.SessionDomain) {
log.Warnf("Request host %q is not a subdomain of %q", r.Host, config.SessionDomain)
}
return &state{
FirstVisitedURL: s + "://" + r.Host + r.URL.String(),
}
}
}

// createState creates the state parameter from the incoming request, stores
// it in the session store and sets a cookie with the session key.
// It returns the session key, which can be used as the state value to start
// an OIDC authentication request.
func createState(r *http.Request, w http.ResponseWriter,
store sessions.Store) (string, error) {
store sessions.Store, sf StateFunc) (string, error) {

s := newState(r.URL.Path)
s := sf(r.URL.Path)
session := sessions.NewSession(store, oidcStateCookie)
session.Options.MaxAge = int(20 * time.Minute)
session.Values[sessionValueState] = *s
Expand Down

0 comments on commit cc31227

Please sign in to comment.