mirror of
https://github.com/oauth2-proxy/oauth2-proxy.git
synced 2026-10-05 14:11:14 +02:00
Add --bearer-token-login-fallback option (#2924)
* add --deny-invalid-bearer-tokens * update changelog * PR feedback, update api-routes description * update --api-routes description * revert load_test fix that I needed locally --------- Co-authored-by: Justin Ryan <j.ryan@mwam.com>
This commit is contained in:
co-authored by
Justin Ryan
parent
bb6ff4ed14
commit
8abdbb5a18
@@ -15,10 +15,11 @@ import (
|
||||
|
||||
const jwtRegexFormat = `^ey[a-zA-Z0-9_-]*\.ey[a-zA-Z0-9_-]*\.[a-zA-Z0-9_-]+$`
|
||||
|
||||
func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionFunc) alice.Constructor {
|
||||
func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionFunc, bearerTokenLoginFallback bool) alice.Constructor {
|
||||
js := &jwtSessionLoader{
|
||||
jwtRegex: regexp.MustCompile(jwtRegexFormat),
|
||||
sessionLoaders: sessionLoaders,
|
||||
jwtRegex: regexp.MustCompile(jwtRegexFormat),
|
||||
sessionLoaders: sessionLoaders,
|
||||
denyInvalidJWTs: !bearerTokenLoginFallback,
|
||||
}
|
||||
return js.loadSession
|
||||
}
|
||||
@@ -26,14 +27,16 @@ func NewJwtSessionLoader(sessionLoaders []middlewareapi.TokenToSessionFunc) alic
|
||||
// jwtSessionLoader is responsible for loading sessions from JWTs in
|
||||
// Authorization headers.
|
||||
type jwtSessionLoader struct {
|
||||
jwtRegex *regexp.Regexp
|
||||
sessionLoaders []middlewareapi.TokenToSessionFunc
|
||||
jwtRegex *regexp.Regexp
|
||||
sessionLoaders []middlewareapi.TokenToSessionFunc
|
||||
denyInvalidJWTs bool
|
||||
}
|
||||
|
||||
// loadSession attempts to load a session from a JWT stored in an Authorization
|
||||
// header within the request.
|
||||
// If no authorization header is found, or the header is invalid, no session
|
||||
// will be loaded and the request will be passed to the next handler.
|
||||
// Or if the JWT is invalid and denyInvalidJWTs, return 403 now.
|
||||
// If a session was loaded by a previous handler, it will not be replaced.
|
||||
func (j *jwtSessionLoader) loadSession(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
|
||||
@@ -49,6 +52,10 @@ func (j *jwtSessionLoader) loadSession(next http.Handler) http.Handler {
|
||||
session, err := j.getJwtSession(req)
|
||||
if err != nil {
|
||||
logger.Errorf("Error retrieving session from token in Authorization header: %v", err)
|
||||
if j.denyInvalidJWTs {
|
||||
http.Error(rw, http.StatusText(http.StatusForbidden), http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Add the session to the scope if it was found
|
||||
@@ -58,7 +65,7 @@ func (j *jwtSessionLoader) loadSession(next http.Handler) http.Handler {
|
||||
}
|
||||
|
||||
// getJwtSession loads a session based on a JWT token in the authorization header.
|
||||
// (see the config options skip-jwt-bearer-tokens and extra-jwt-issuers)
|
||||
// (see the config options skip-jwt-bearer-tokens, extra-jwt-issuers, and bearer-token-login-fallback)
|
||||
func (j *jwtSessionLoader) getJwtSession(req *http.Request) (*sessionsapi.SessionState, error) {
|
||||
auth := req.Header.Get("Authorization")
|
||||
if auth == "" {
|
||||
|
||||
@@ -92,6 +92,7 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
|
||||
authorizationHeader string
|
||||
existingSession *sessionsapi.SessionState
|
||||
expectedSession *sessionsapi.SessionState
|
||||
expectedStatus int
|
||||
}
|
||||
|
||||
DescribeTable("with an authorization header",
|
||||
@@ -114,12 +115,13 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
|
||||
// Create the handler with a next handler that will capture the session
|
||||
// from the scope
|
||||
var gotSession *sessionsapi.SessionState
|
||||
handler := NewJwtSessionLoader(sessionLoaders)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
handler := NewJwtSessionLoader(sessionLoaders, true)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotSession = middlewareapi.GetRequestScope(r).Session
|
||||
}))
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
Expect(gotSession).To(Equal(in.expectedSession))
|
||||
Expect(rw.Code).To(Equal(200))
|
||||
},
|
||||
Entry("<no value>", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: "",
|
||||
@@ -163,6 +165,83 @@ Nnc3a3lGVWFCNUMxQnNJcnJMTWxka1dFaHluYmI4Ongtb2F1dGgtYmFzaWM=`
|
||||
}),
|
||||
)
|
||||
|
||||
DescribeTable("with an authorization header, denyInvalidJWTs",
|
||||
func(in jwtSessionLoaderTableInput) {
|
||||
scope := &middlewareapi.RequestScope{
|
||||
Session: in.existingSession,
|
||||
}
|
||||
|
||||
// Set up the request with the authorization header and a request scope
|
||||
req := httptest.NewRequest("", "/", nil)
|
||||
req.Header.Set("Authorization", in.authorizationHeader)
|
||||
req = middlewareapi.AddRequestScope(req, scope)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
|
||||
sessionLoaders := []middlewareapi.TokenToSessionFunc{
|
||||
middlewareapi.CreateTokenToSessionFunc(verifier),
|
||||
}
|
||||
|
||||
// Create the handler with a next handler that will capture the session
|
||||
// from the scope
|
||||
var gotSession *sessionsapi.SessionState
|
||||
handler := NewJwtSessionLoader(sessionLoaders, false)(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotSession = middlewareapi.GetRequestScope(r).Session
|
||||
}))
|
||||
handler.ServeHTTP(rw, req)
|
||||
|
||||
Expect(gotSession).To(Equal(in.expectedSession))
|
||||
Expect(rw.Code).To(Equal(in.expectedStatus))
|
||||
},
|
||||
Entry("<no value>", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: "",
|
||||
existingSession: nil,
|
||||
expectedSession: nil,
|
||||
expectedStatus: 200,
|
||||
}),
|
||||
Entry("abcdef", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: "abcdef",
|
||||
existingSession: nil,
|
||||
expectedSession: nil,
|
||||
expectedStatus: 403,
|
||||
}),
|
||||
Entry("abcdef (with existing session)", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: "abcdef",
|
||||
existingSession: &sessionsapi.SessionState{User: "user"},
|
||||
expectedSession: &sessionsapi.SessionState{User: "user"},
|
||||
expectedStatus: 200,
|
||||
}),
|
||||
Entry("Bearer <verifiedToken>", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: fmt.Sprintf("Bearer %s", verifiedToken),
|
||||
existingSession: nil,
|
||||
expectedSession: verifiedSession,
|
||||
expectedStatus: 200,
|
||||
}),
|
||||
Entry("Bearer <nonVerifiedToken>", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: fmt.Sprintf("Bearer %s", nonVerifiedToken),
|
||||
existingSession: nil,
|
||||
expectedSession: nil,
|
||||
expectedStatus: 403,
|
||||
}),
|
||||
Entry("Bearer <verifiedToken> (with existing session)", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: fmt.Sprintf("Bearer %s", verifiedToken),
|
||||
existingSession: &sessionsapi.SessionState{User: "user"},
|
||||
expectedSession: &sessionsapi.SessionState{User: "user"},
|
||||
expectedStatus: 200,
|
||||
}),
|
||||
Entry("Basic Base64(<nonVerifiedToken>:) (No password)", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: "Basic ZXlKZm9vYmFyLmV5SmZvb2Jhci4xMjM0NWFzZGY6",
|
||||
existingSession: nil,
|
||||
expectedSession: nil,
|
||||
expectedStatus: 403,
|
||||
}),
|
||||
Entry("Basic Base64(<verifiedToken>:x-oauth-basic) (Sentinel password)", jwtSessionLoaderTableInput{
|
||||
authorizationHeader: fmt.Sprintf("Basic %s", verifiedTokenXOAuthBasicBase64),
|
||||
existingSession: nil,
|
||||
expectedSession: verifiedSession,
|
||||
expectedStatus: 200,
|
||||
}),
|
||||
)
|
||||
})
|
||||
|
||||
Context("getJWTSession", func() {
|
||||
|
||||
Reference in New Issue
Block a user