Skip to content

Commit

Permalink
feat: introduce auth scheme and jumping to next authentication
Browse files Browse the repository at this point in the history
  • Loading branch information
Marlinc authored and aeneasr committed Sep 10, 2022
1 parent 708ad9d commit 64e332f
Show file tree
Hide file tree
Showing 5 changed files with 78 additions and 4 deletions.
15 changes: 15 additions & 0 deletions .schema/config.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,11 @@
"title": "Header",
"type": "string",
"description": "The header (case insensitive) that must contain a token for request authentication.\n It can't be set along with query_parameter or cookie."
},
"auth_scheme": {
"title": "Auth scheme",
"type": "string",
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
}
}
},
Expand Down Expand Up @@ -619,6 +624,11 @@
"title": "Header",
"type": "string",
"description": "The header (case insensitive) that must contain a token for request authentication.\n It can't be set along with query_parameter or cookie."
},
"auth_scheme": {
"title": "Auth scheme",
"type": "string",
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
}
}
},
Expand Down Expand Up @@ -842,6 +852,11 @@
"title": "Header",
"type": "string",
"description": "The header (case insensitive) that must contain a token for request authentication.\n It can't be set along with query_parameter or cookie."
},
"auth_scheme": {
"title": "Auth scheme",
"type": "string",
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
}
}
},
Expand Down
17 changes: 13 additions & 4 deletions helper/bearer.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ const (

type BearerTokenLocation struct {
Header *string `json:"header"`
AuthScheme *string `json:"auth_scheme"`
QueryParameter *string `json:"query_parameter"`
Cookie *string `json:"cookie"`
}
Expand All @@ -39,7 +40,11 @@ func BearerTokenFromRequest(r *http.Request, tokenLocation *BearerTokenLocation)
if tokenLocation != nil {
if tokenLocation.Header != nil {
if *tokenLocation.Header == defaultAuthorizationHeader {
return DefaultBearerTokenFromRequest(r)
authScheme := "Bearer"
if tokenLocation.AuthScheme != nil {
authScheme = *tokenLocation.AuthScheme
}
return DefaultBearerTokenFromRequest(r, authScheme)
}
return r.Header.Get(*tokenLocation.Header)
} else if tokenLocation.QueryParameter != nil {
Expand All @@ -53,13 +58,17 @@ func BearerTokenFromRequest(r *http.Request, tokenLocation *BearerTokenLocation)
}
}

return DefaultBearerTokenFromRequest(r)
return DefaultBearerTokenFromRequest(r, "Bearer")
}

func DefaultBearerTokenFromRequest(r *http.Request) string {
func DefaultBearerTokenFromRequest(r *http.Request, authScheme string) string {
token := r.Header.Get(defaultAuthorizationHeader)
if authScheme == "" {
return token
}

split := strings.SplitN(token, " ", 2)
if len(split) != 2 || !strings.EqualFold(strings.ToLower(split[0]), "bearer") {
if len(split) != 2 || !strings.EqualFold(strings.ToLower(split[0]), strings.ToLower(authScheme)) {
return ""
}
return split[1]
Expand Down
18 changes: 18 additions & 0 deletions helper/bearer_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,4 +74,22 @@ func TestBearerTokenFromRequest(t *testing.T) {
token := helper.BearerTokenFromRequest(request, &tokenLocation)
assert.Equal(t, expectedToken, token)
})
t.Run("case=token should be received from authorization header with custom auth scheme if custom location is set to header and token is present", func(t *testing.T) {
expectedToken := "token"
customHeaderName := "Authorization"
customAuthScheme := "AccessToken"
request := &http.Request{Header: http.Header{customHeaderName: {customAuthScheme + " " + expectedToken}}}
tokenLocation := helper.BearerTokenLocation{Header: &customHeaderName, AuthScheme: &customAuthScheme}
token := helper.BearerTokenFromRequest(request, &tokenLocation)
assert.Equal(t, expectedToken, token)
})
t.Run("case=token should be received from authorization header with an empty custom auth scheme if custom location is set to header and token is present", func(t *testing.T) {
expectedToken := "token"
customHeaderName := "Authorization"
customAuthScheme := ""
request := &http.Request{Header: http.Header{customHeaderName: {expectedToken}}}
tokenLocation := helper.BearerTokenLocation{Header: &customHeaderName, AuthScheme: &customAuthScheme}
token := helper.BearerTokenFromRequest(request, &tokenLocation)
assert.Equal(t, expectedToken, token)
})
}
27 changes: 27 additions & 0 deletions pipeline/authn/authenticator_bearer_token_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,17 @@ func TestAuthenticatorBearerToken(t *testing.T) {
expectErr: true,
expectExactErr: ErrAuthenticatorNotResponsible,
},
{
d: "should return error saying that authenticator is not responsible for validating the request, as the session store returns HTTP 406 Not Acceptable",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}},
setup: func(t *testing.T, m *httprouter.Router) {
m.GET("/", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
w.WriteHeader(406)
})
},
expectErr: true,
expectExactErr: ErrAuthenticatorNotResponsible,
},
{
d: "should fail because session store returned 400",
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}},
Expand All @@ -85,6 +96,22 @@ func TestAuthenticatorBearerToken(t *testing.T) {
Extra: map[string]interface{}{"foo": "bar"},
},
},
{
d: "should pass because session store returned 200",
r: &http.Request{Header: http.Header{"Authorization": {"AccessToken token"}}, URL: &url.URL{Path: ""}},
config: []byte(`{"token_from": {"header": "Authorization", "auth_scheme": "AccessToken"}}`),
setup: func(t *testing.T, m *httprouter.Router) {
m.GET("/", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
w.WriteHeader(200)
w.Write([]byte(`{"sub": "123", "extra": {"foo": "bar"}}`))
})
},
expectErr: false,
expectSess: &AuthenticationSession{
Subject: "123",
Extra: map[string]interface{}{"foo": "bar"},
},
},
{
d: "should pass through method, path, and headers to auth server; should NOT pass through query parameters by default for backwards compatibility",
r: &http.Request{Header: http.Header{"Authorization": {"bearer zyx"}}, URL: &url.URL{Path: "/users/123", RawQuery: "query=string"}, Method: "PUT"},
Expand Down
5 changes: 5 additions & 0 deletions pipeline/authn/authenticator_cookie_session.go
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,11 @@ func forwardRequestToSessionStore(r *http.Request, cf AuthenticatorForwardConfig

defer res.Body.Close()

// HTTP 406 Not Acceptable
if res.StatusCode == http.StatusNotAcceptable {
return nil, errors.WithStack(ErrAuthenticatorNotResponsible)
}

if res.StatusCode == http.StatusOK {
body, err := ioutil.ReadAll(res.Body)
if err != nil {
Expand Down

0 comments on commit 64e332f

Please sign in to comment.