Skip to content

Commit 64e332f

Browse files
Marlincaeneasr
authored andcommitted
feat: introduce auth scheme and jumping to next authentication
1 parent 708ad9d commit 64e332f

File tree

5 files changed

+78
-4
lines changed

5 files changed

+78
-4
lines changed

.schema/config.schema.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,11 @@
456456
"title": "Header",
457457
"type": "string",
458458
"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."
459+
},
460+
"auth_scheme": {
461+
"title": "Auth scheme",
462+
"type": "string",
463+
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
459464
}
460465
}
461466
},
@@ -619,6 +624,11 @@
619624
"title": "Header",
620625
"type": "string",
621626
"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."
627+
},
628+
"auth_scheme": {
629+
"title": "Auth scheme",
630+
"type": "string",
631+
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
622632
}
623633
}
624634
},
@@ -842,6 +852,11 @@
842852
"title": "Header",
843853
"type": "string",
844854
"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."
855+
},
856+
"auth_scheme": {
857+
"title": "Auth scheme",
858+
"type": "string",
859+
"description": "The auth scheme to accept in case the header is set to Authorization, this is by default set to Bearer."
845860
}
846861
}
847862
},

helper/bearer.go

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ const (
3131

3232
type BearerTokenLocation struct {
3333
Header *string `json:"header"`
34+
AuthScheme *string `json:"auth_scheme"`
3435
QueryParameter *string `json:"query_parameter"`
3536
Cookie *string `json:"cookie"`
3637
}
@@ -39,7 +40,11 @@ func BearerTokenFromRequest(r *http.Request, tokenLocation *BearerTokenLocation)
3940
if tokenLocation != nil {
4041
if tokenLocation.Header != nil {
4142
if *tokenLocation.Header == defaultAuthorizationHeader {
42-
return DefaultBearerTokenFromRequest(r)
43+
authScheme := "Bearer"
44+
if tokenLocation.AuthScheme != nil {
45+
authScheme = *tokenLocation.AuthScheme
46+
}
47+
return DefaultBearerTokenFromRequest(r, authScheme)
4348
}
4449
return r.Header.Get(*tokenLocation.Header)
4550
} else if tokenLocation.QueryParameter != nil {
@@ -53,13 +58,17 @@ func BearerTokenFromRequest(r *http.Request, tokenLocation *BearerTokenLocation)
5358
}
5459
}
5560

56-
return DefaultBearerTokenFromRequest(r)
61+
return DefaultBearerTokenFromRequest(r, "Bearer")
5762
}
5863

59-
func DefaultBearerTokenFromRequest(r *http.Request) string {
64+
func DefaultBearerTokenFromRequest(r *http.Request, authScheme string) string {
6065
token := r.Header.Get(defaultAuthorizationHeader)
66+
if authScheme == "" {
67+
return token
68+
}
69+
6170
split := strings.SplitN(token, " ", 2)
62-
if len(split) != 2 || !strings.EqualFold(strings.ToLower(split[0]), "bearer") {
71+
if len(split) != 2 || !strings.EqualFold(strings.ToLower(split[0]), strings.ToLower(authScheme)) {
6372
return ""
6473
}
6574
return split[1]

helper/bearer_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,4 +74,22 @@ func TestBearerTokenFromRequest(t *testing.T) {
7474
token := helper.BearerTokenFromRequest(request, &tokenLocation)
7575
assert.Equal(t, expectedToken, token)
7676
})
77+
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) {
78+
expectedToken := "token"
79+
customHeaderName := "Authorization"
80+
customAuthScheme := "AccessToken"
81+
request := &http.Request{Header: http.Header{customHeaderName: {customAuthScheme + " " + expectedToken}}}
82+
tokenLocation := helper.BearerTokenLocation{Header: &customHeaderName, AuthScheme: &customAuthScheme}
83+
token := helper.BearerTokenFromRequest(request, &tokenLocation)
84+
assert.Equal(t, expectedToken, token)
85+
})
86+
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) {
87+
expectedToken := "token"
88+
customHeaderName := "Authorization"
89+
customAuthScheme := ""
90+
request := &http.Request{Header: http.Header{customHeaderName: {expectedToken}}}
91+
tokenLocation := helper.BearerTokenLocation{Header: &customHeaderName, AuthScheme: &customAuthScheme}
92+
token := helper.BearerTokenFromRequest(request, &tokenLocation)
93+
assert.Equal(t, expectedToken, token)
94+
})
7795
}

pipeline/authn/authenticator_bearer_token_test.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,17 @@ func TestAuthenticatorBearerToken(t *testing.T) {
6060
expectErr: true,
6161
expectExactErr: ErrAuthenticatorNotResponsible,
6262
},
63+
{
64+
d: "should return error saying that authenticator is not responsible for validating the request, as the session store returns HTTP 406 Not Acceptable",
65+
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}},
66+
setup: func(t *testing.T, m *httprouter.Router) {
67+
m.GET("/", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
68+
w.WriteHeader(406)
69+
})
70+
},
71+
expectErr: true,
72+
expectExactErr: ErrAuthenticatorNotResponsible,
73+
},
6374
{
6475
d: "should fail because session store returned 400",
6576
r: &http.Request{Header: http.Header{"Authorization": {"bearer token"}}, URL: &url.URL{Path: ""}},
@@ -85,6 +96,22 @@ func TestAuthenticatorBearerToken(t *testing.T) {
8596
Extra: map[string]interface{}{"foo": "bar"},
8697
},
8798
},
99+
{
100+
d: "should pass because session store returned 200",
101+
r: &http.Request{Header: http.Header{"Authorization": {"AccessToken token"}}, URL: &url.URL{Path: ""}},
102+
config: []byte(`{"token_from": {"header": "Authorization", "auth_scheme": "AccessToken"}}`),
103+
setup: func(t *testing.T, m *httprouter.Router) {
104+
m.GET("/", func(w http.ResponseWriter, _ *http.Request, _ httprouter.Params) {
105+
w.WriteHeader(200)
106+
w.Write([]byte(`{"sub": "123", "extra": {"foo": "bar"}}`))
107+
})
108+
},
109+
expectErr: false,
110+
expectSess: &AuthenticationSession{
111+
Subject: "123",
112+
Extra: map[string]interface{}{"foo": "bar"},
113+
},
114+
},
88115
{
89116
d: "should pass through method, path, and headers to auth server; should NOT pass through query parameters by default for backwards compatibility",
90117
r: &http.Request{Header: http.Header{"Authorization": {"bearer zyx"}}, URL: &url.URL{Path: "/users/123", RawQuery: "query=string"}, Method: "PUT"},

pipeline/authn/authenticator_cookie_session.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -177,6 +177,11 @@ func forwardRequestToSessionStore(r *http.Request, cf AuthenticatorForwardConfig
177177

178178
defer res.Body.Close()
179179

180+
// HTTP 406 Not Acceptable
181+
if res.StatusCode == http.StatusNotAcceptable {
182+
return nil, errors.WithStack(ErrAuthenticatorNotResponsible)
183+
}
184+
180185
if res.StatusCode == http.StatusOK {
181186
body, err := ioutil.ReadAll(res.Body)
182187
if err != nil {

0 commit comments

Comments
 (0)